xargs の !(%) が正しく動作しないのはなぜですか?

xargs の !(%) が正しく動作しないのはなぜですか?

次のコードを使用して、最後のファイルを省略し、残りのファイルを宛先フォルダーにコピーしようとしています。

ls -Art | tail -n 1 | xargs -I% cp !(%) ~/test_dst/folder1

最初の 2 つのパイプは最新のファイルの名前を取得し、これが xargs にパイプされて cp コマンドに追加されます。ここで、extglob の ! ワイルドカードを使用して、他のすべてをコピーします。ただし、問題は、これによりすべてのファイルがコピーされることです。最初の部分が何を印刷するか (set -x がオン) を判別して、これをデバッグしようとしました。

> ~/test_src/folder1$ ls -Art | tail -n 1
+ tail -n 1
+ ls --color=auto -Art
file4

成功しました! これが実際の正しいファイルです! 次にワイルドカードを使用して印刷してみました:

> ~/test_src/folder1$ ls -Art | tail -n 1 | xargs -I% echo !(%)
+ tail -n 1
+ ls --color=auto -Art
+ xargs -I% echo file1 file2 file3 file4
file1 file2 file3 file4

省略されたファイルが含まれていますか? コマンド自体をハードコードしようとしましたが、なんと、実際には正しく動作します。

> ~/test_src/folder1$ echo !(file4)
+ echo file1 file2 file3
file1 file2 file3

何が間違っているのでしょうか? これは基本的にシェル スクリプトの最初の試みであり、私を困惑させています。ヒントがあれば大歓迎です。この特定のメソッドをデバッグしたいのですが (これは私が考えていた方法です)、これを行うための提案や代替メソッドがあればぜひ教えてください。ありがとうございます!

答え1

はい、うまくいきません。結合しようとしている 2 つの機能が間違った順序で解釈されています。

extglobはシェル(コマンドインタープリタ)の機能であり、「trace」出力でわかるように、bashによって展開されます。前に'xargs' コマンドが実際に実行されます。

extglobs (または実際のところ任意の glob) がそのまま xargs (または 'echo'、'cp') に渡された場合、xargs はそれをどう処理すればよいかわかりません。たとえば、'cp' は "!(%)" または "!(file1)" がコピーされるファイル名であると認識します。

一方、xargsはないシェルの一部です。他のプログラムと同様にスタンドアロン プログラムであり、bash は「%」の意味を認識せず、xargs に別のコマンドを渡していることも認識しません。

したがって、!(%)extglobが展開されるときは、一度コマンド全体に対して – xargsの入力ごとに1回ではなく、%Bashがコマンド内で認識するリテラルに対して1回だけ。もちろん、これは次の結果になります。名前が付けられていないすべてのファイル%


これを実現する方法の1つは、xargsをシェル独自の展開に置き換えることです。xargsとは異なり、シェル変数は展開されます。前にextglobs なので、ファイル名を変数に格納すると...

except_file=$(ls -Art | tail -n 1)

...extglob でこれを使用できます:

cp !("$except_file") ~/test_dst/folder1

(この2つを組み合わせて とすることもできますcp !("$(ls...)") ~/...。)

逆に、xargsは残してextglobを削除することも可能です。最後のファイルだけを取得してtail、他のすべてを取得するために余分な手順を踏む代わりに、直接取得することができます。すべてを除く最後に使用したファイルhead('head' と 'tail' の両方の負のパラメータは「最後の N を除く」を意味します):

ls -Art | head -n -1 | xargs -I% cp % ~/test_dst/folder1

または、システムに GNU Coreutils があり、オプションが設定されcpていると仮定すると、xargs にすべてのファイルに対して一度に1 つずつ呼び出す作業をさせるには、次のようにします。cp -t <target>

ls -Art | head -n -1 | xargs -d '\n' cp -t ~/test_dst/folder1

補足:Linuxのファイル名には改行が含まれることがあります。上記の例の多くは、そのようなファイル名は存在しないことを前提としています。なぜなら、実際にはそうすべきではないからです。しかし、未知のシステムに展開するスクリプトを書くときに考慮すべきことであり、「防御的」プログラミングでは通常、改行で区切られたリストではなく、ヌルで区切られたリストを扱うために や などのツールが必要になりますfind ... -print0xargs -0(シェルのワイルドカードは通常、これをうまく処理します。)

関連情報