find
{}
「このファイル」(ish)を表すために使用されます。次のようににさまざまなファイルを入力できますmyprog
。
find ./tests/ -name *.in -exec myprog -i {} \;
中に名前を変更する方法はありませんか{}
?私の場合は、-i
入力ファイルと出力を定義し、-o
出力でファイル名を少し変更して「a.in」が「a.out」を生成したいと思いました。理想的には、次のことを達成したいと思います。
find ./tests/ -name *.in -exec myprog -i {} -o {}.out \;
また、出力ディレクトリのパスが異なる場合があります。この場合、出力は到着しない可能性がありますが/tests/
到着する可能性があります/tests_20220615/
。
例を含む複数のページを見てみましたが、find
同様のものが表示されないので、「いいえ」ではありませんか?
Bashまたはzshでループを使用してこれを実行する方法があることはわかっていますが、可能なトラップのリストは素晴らしい( "nullglob"?!)、これを行うことができれば、find
この愚かな奴にとってより安全に見えます。
答え1
find
ファイルパスを変更できず、一部の実装find
では他のコンテンツと関連付けることができず、{}
一部はマルチパスもサポートしていませんが、{}
変換を実行できるシェルなどの一部のコマンドは常に実行できます。
find ./tests/ -name '*.in' -type f -exec sh -c '
ret=0
for file do
myprog -i "$file" -o "${file%.*}.out" || ret="$?"
done
exit "$ret"' sh {} +
myprog
上記では、直接実行するのではなく、検出されたファイルへのパスsh
とともにいくつかのインラインコードを実行して渡します(できるだけ多くのファイルを渡すのではなく){} +
。{} ';'
sh
これらのファイルを順番に繰り返し、拡張子の削除myprog
などの変換を適用して呼び出します。${file%.*}
周囲の引用符を参照してください*.in
。これがなければ、コマンドを実行するシェルはパターンをそのまま渡すfind
のではなく、名前で終わる現在のディレクトリのファイルのリストに展開しようとします。.in
find
sh
上記で呼び出しが失敗した場合、失敗した終了myprog
状態で終了すると言いました。失敗はの終了ステータスに反映されるため、必要に応じてアクションを実行するfind
か、errexit
このオプションが有効な場合はスクリプトを終了できます。ただし、最初の失敗時に中断することは不可能ですmyprog
。
シェルを使用している場合は、zsh
内部で見つけることもできます。
set -o errexit
for file (./tests/**/*.in(ND.)) myprog -i $file -o $file:r.out
最初の失敗時に終了し、語彙順でリストを処理します(oN
この順序を無効にするには、いつでもglob修飾子を追加できます)。
find
別のアプローチは、ファイルを印刷して変換を実行するいくつかのコマンドにパイプしてからパイプすることですxargs
。
find ./tests/ -name '*.in' -type f -print0 |
gawk -v RS='\0' -v ORS='\0' -v OFS='\0' '
{
filein = fileout = $0; sub(/\.in$/, ".out", fileout)
print "-i", filein, "-o", fileout
}' | xargs -r0 -n4 myprog
呼び出しが失敗すると、xargs
ゼロ以外の終了状態が再び返されます。myprog
GNUはそのオプションを使用して複数の呼び出しを並列に実行xargs
できます。-P
あるいは、perl
後処理して実行させることもできます。
find ./tests/ -name '*.in' -type f -print0 |
perl -l -0ne '
system("myprog", "-i", $_, "-o", s/\.in\Z/.out/r) == 0 or
$ret = 1;
END {exit $ret}'
シェルをfind
設定しない限り、(サポートされている場合)メソッドの後処理された出力は、失敗した終了状態(存在する場合)(たとえば、特定のディレクトリに入ることができない場合)をマスクします。pipefail
パイプを使用すると、myprog
標準入力にも影響します(たとえば、ユーザーにメッセージを表示する必要がある場合など)。 GNUは標準入力を開き、他のxargs
メソッドはそのまま残ります。これは / のパイプになることを意味します。/dev/null
perl
find
gawk
答え2
あなたは素晴らしい答えを受けました。しかし、あなたの質問の前提は、それが何であるべきかではないと思います。 bashでループを書くことを恐れてはいけませんが、それでも他のユーティリティに注意する必要があることを考慮すると、この場合bashを使用しない理由はありません。
この例では、次のように単純に実行しても問題はありません。
for file in test/*; do
[[ -e "$file" ]] || continue
echo cp "$file" "${file/test/tests_20220615}.out";
done
cp test/1.in test_20220615/1.out
cp test/10.in test_20220615/10.out
cp test/2.in test_20220615/2.out
cp test/3.in test_20220615/3.out
cp test/4.in test_20220615/4.out
cp test/5.in test_20220615/5.out
...
これはデフォルトのnullglob
動作に関係なく機能します。特に、ファイルを確認する条件を追加し([[ -e "$i" ]]
その名前のファイルが存在する場合はtrue)、出力がわからない場合はそこにechoステートメントを投げて(より良い場合printf
)、すべてが正しいことを確認してください。