いくつかのリストで、デフォルトの名前が指定されている何百ものファイルを見つける必要があります(と呼びますbaseNames
)。次に、基本名+指定された3つの拡張子を検索する必要があります。
例:入力リストから抽出されたデフォルト名の1つがで、指定されたFOO
拡張子が.txt
、、.csv
と仮定されます.py
。だから、、FOO.txt
を見つける必要がありますFOO.csv
。FOO.py
私のbashスクリプトの現在の方法は次のとおりです。
for bn in ${baseNames}; do
find ${searchDir} '(' -name "$bn.txt" -o -name "$bn.csv" -o -name "$bn.py" ')'
done
これは機能しますが、非効率的です。各デフォルト名に対してジョブfind
全体を再実行する必要があります。searchDir
これにはかなりの数のファイルが含まれているため、時間がかかります。
find
オプションまたはパイプで検索する必要があるファイルのリストを提供する方法はありますか?
明らかに知っているが、-name ... -or
何百ものファイルがある場合、このアプローチは明らかに実用的ではありません。単純化のために拡張子を無視することもできます。 find
.
答え1
配列を使用してください。例えば
#!/bin/bash
baseNames=(FOO BAR BAZ)
findNames=('(')
for bn in "${baseNames[@]}"; do
for ext in txt csv py; do
findNames+=("$bn.$ext" '-o' '-name')
done
done
# replace the final '-o' and '-name' in the array with a close parenthesis
unset 'findNames[-1]'
findNames[-1]=')'
# If using a version of bash before v4.3, use:
#unset 'findNames[${#findNames[@]}-1]'
#findNames[${#findNames[@]}-1]=')'
declare -p findNames
出力は次のようになりますdeclare -p
(改行とスペースを追加して読みやすくなりました)。
declare -a findNames=(
[0]="("
[1]="-name" [2]="FOO.txt" [3]="-o" [4]="-name" [5]="FOO.csv"
[6]="-o" [7]="-name" [8]="FOO.py" [9]="-o" [10]="-name" [11]="BAR.txt"
[12]="-o" [13]="-name" [14]="BAR.csv" [15]="-o" [16]="-name" [17]="BAR.py"
[18]="-o" [19]="-name" [20]="BAZ.txt" [21]="-o" [22]="-name" [23]="BAZ.csv"
[24]="-o" [25]="-name" [26]="BAZ.py"
[27]=")"
)
で配列を使用するには、find
次のことが必要です。
searchDir="./"
find "$searchDir" "${findNames[@]}"
これにより、次のfindコマンドが実行されます(読みやすくするために改行が追加されました)。
find ./ ( -name FOO.txt -o -name FOO.csv -o -name FOO.py \
-o -name BAR.txt -o -name BAR.csv -o -name BAR.py \
-o -name BAZ.txt -o -name BAZ.csv -o -name BAZ.py )
そしてここでエスケープする必要はありません。なぜなら、シェルはそれをサブシェルの起動を指示するのではなく、リテラル引数(配列はbash拡張)として扱うからです(
。)
シェルに入力する場合は、エスケープまたは引用する必要があります。
答え2
次のsh
スクリプトは、1行に1つの名前を含むファイルからデフォルトの名前を読み取りnames
(名前にスペースなどが含まれている場合は引用符で囲む必要があります)、その名前(一度に50)を含むインラインスクリプトのバッチを呼び出します。sh -c
。データが単一の呼び出しと比較して長すぎるリストに展開される場合に備えて、入力をバッチに分割します(合計結合長が入力データの長さを超えるfind
コマンドを構成する必要があります。ここで、ファイル名のサフィックスの数は次のとおりです。同じです。探す)。n
n
インラインスクリプトは、-name
指定された基本名に基づいてテストの「ORリスト」を作成しますfind
。各デフォルト名は、3つのファイル名のサフィックスとバリアントを含むリストに入力されます.txt
。.csv
.py
このリストは場所パラメータリストに保存されます"$@"
。
リストが完成したら、find
関数を呼び出してディレクトリ内または下のこれらの名前と一致する一般的なファイルを見つけます$topdir
。
topdir=$HOME
<names xargs -L 50 sh -c '
topdir=$1; shift
for name do
for suffix in txt csv py; do
set -- "$@" -o -name "$name.$suffix"
done
shift # shift off current base name
done
shift # shift off the initial "-o"
find "$topdir" -type f \( "$@" \) -print
' sh "$topdir"
50より小さい数字で実行し、sh -x -c
代わりにsh -c
インラインスクリプトが実際に実行しているコマンドを確認してください。
名前付き配列とシェルを使用するには、次の手順を実行しますbash
。
topdir=$HOME
<names xargs -L 50 bash -c '
topdir=$1; shift
unset tests
for name do
for suffix in txt csv py; do
tests+=( -o -name "$name.$suffix" )
done
done
find "$topdir" -type f \( "${tests[@]:1}" \) -print
' bash "$topdir"
ここでは、tests
位置引数リストの代わりに配列が使用されます。"${tests[@]:1}"
最初の要素(例:)を除いて、配列要素のリストに展開するのは奇妙です-o
。
ただし、以下を使用する場合は、bash
グロービングツール(元のシェルから継承)を使用することもできますksh
。
shopt -s extglob globstar dotglob nullglob
topdir=$HOME
printf -v pattern '%s/**/@(%s).@(txt|csv|py)' "$topdir" "$(paste -s -d '|' - <names)"
eval "pathnames=( $pattern )"
# The following loop is only for illustration.
# If you really just wanted to list the names, use
# printf '%s\n' "${pathnames[@]}"
for pathname in "${pathnames[@]}"; do
printf '%s\n' "$pathname"
done
これはファイルの内容に基づいて拡張されたワイルドカードパターンを構築しますnames
。このパターンは、最終的に次のように見えることがあります。
/home/myself/**/@(name1|name2|name3).@(txt|csv|py)
...あなたが興味のある名前と一致します。 (ディレクトリなどで一般的なファイルをフィルタリングするために)、ループ内ですべてのファイルタイプテストを直接実行する必要があります。
スクリプトの上部に設定されているシェルオプションを使用すると、拡張モード@(...|...)
()の使用、サブディレクトリ()の下向きの一致のextglob
使用、非表示のサブディレクトリ()にある、または存在する名前を非表示にできます。また、一致するものがまったくない場合、パターンが消えるようにtaを設定しました。**
globstar
dotglob
nullglob
答え3
zshを使用する(引用符なしでこのパラメータを拡張すると、コードはzsh構文を使用します):
names=(foo bar baz)
exts=(txt csv py)
print -rC1 - **/(${(~j[|])names}).${(~j[|])exts})(ND)
ここで、aはグローバル演算子と見なされ、配列${(j[|])array}
の要素を連結するために使用されます|
。 .|
~
N
nullglob
D
dotglob
またはもちろんこれを行う:
print -rC1 - **/(foo|bar|baz).(cvs|py|txt)(ND)
一部のファイルで1行に1つの名前と拡張子を見つけた場合は、次のようにします。
names=( ${(f)"$(< names.txt)"} )
exts=( ${(f)"$(< exts.txt)"} )
次のようにすることもできます。
print -rC1 - **/$^names.$^exts(ND)
しかし、これは、名前+拡張の各組合せに対して再帰的グローブを拡張するので効率が悪い。
検索用find
:
cmd=(find . '(') or=()
for name ($^names.$^exts) cmd+=($or -name ${(b)name}) or=(-o)
cmd+=(')')
$cmd