web-dev-qa-db-ja.com

生成されたファイル名のリストを引数リストとして使用-スペースあり

findが収集したファイル名のリストを使用してスクリプトを起動しようとしています。特別なことは何もありません。次のようになります。

_$ myscript `find . -name something.txt`
_

問題は、パス名の一部にスペースが含まれているため、引数の展開時に2つの無効な名前に分割されることです。通常は名前を引用符で囲みますが、ここでは逆引用展開によって挿入されています。私はfindの出力をフィルタリングして、各ファイル名を引用符で囲みましたが、bashがそれらを認識するまでに、それらを取り除くには遅すぎて、ファイル名の一部として扱われます。

_$ myscript `find . -name something.txt | sed 's/.*/"&"/'`
No such file or directory: '"./somedir/something.txt"'
_

はい、それはコマンドラインの処理方法のルールですが、どうすれば回避できますか?

これは恥ずかしいことですが、私は正しいアプローチを思い付くことができません。 _xargs -0 -n 10000_...を使用してそれを行う方法をついに見つけましたが、それでも私が尋ねたいのはとても醜いハックです。

編集:xargsdoesがすべての引数を単一の引数リストに収集するという事実について混乱しました、特に指示がない限り、またはシステムの制限を超える可能性があります。私を正直に設定してくれたみんなに感謝します!それ以外の場合は、受け入れられた回答を直接指摘しないため、受け入れられた回答を読んでいるときにこれを覚えておいてください。

私は答えを受け入れましたが、私の質問は残ります:バックティック(または$(...))展開でスペースを保護する方法はありませんか? (受け入れられたソリューションは非bashの回答であることに注意してください)。

16
alexis

このようにfindxargsのいくつかの実装を使用して、次のことができます。

_$ find . -type f -print0 | xargs -r0 ./myscript
_

または、通常、findのみ:

_$ find . -type f -exec ./myscript {} +
_

次のサンプルディレクトリがあるとします。

_$ tree
.
|-- dir1
|   `-- a\ file1.txt
|-- dir2
|   `-- a\ file2.txt
|-- dir3
|   `-- a\ file3.txt
`-- myscript

3 directories, 4 files
_

ここで、_./myscript_にこれがあるとします。

_#!/bin/bash

for i in "$@"; do
    echo "file: $i"
done
_

次のコマンドを実行すると、.

_$ find . -type f -print0 | xargs -r0 ./myscript 
file: ./dir2/a file2.txt
file: ./dir3/a file3.txt
file: ./dir1/a file1.txt
file: ./myscript
_

または私が2番目のフォームを次のように使用すると:

_$ find . -type f -exec ./myscript {} +
file: ./dir2/a file2.txt
file: ./dir3/a file3.txt
file: ./dir1/a file1.txt
file: ./myscript
_

細部

find + xargs

上記の2つの方法は、見た目は異なりますが、基本的に同じです。 1つ目は、findからの出力を取得し、_\0_スイッチを介してNULL(_-print0_)を使用して分割して検索することです。 _xargs -0_は、NULLを使用して分割された入力を受け取るように特別に設計されています。この非標準の構文はGNU findおよびxargsによって導入されましたが、最近では、最近のBSDのような他のいくつかにも見られます。_-r_オプションは、myscriptがGNU findで何も見つからないがBSDで見つからない場合に、findを呼び出さないようにするために必要です。

注:このアプローチ全体は、非常に長い文字列を渡さないという事実にかかっています。そうである場合、2回目の_./myscript_の呼び出しは、findの後続の結果の残りで開始されます。

+で検索

これは標準的な方法です(ただし、比較的最近(2005年)にGNU findの実装)に追加されました)。xargsは文字どおりfindに組み込まれています。したがって、findはファイルのリストを見つけ、そのリストを_-exec_の後に指定されたコマンドに適合するだけの数の引数で渡しますこの場合、_{}_は_+_の直前にのみ置くことができます)、必要に応じてコマンドを数回実行します。

なぜ引用しないのですか?

最初の例では、引数を区切るためにNULLを使用することで、引用の問題を完全に回避することにより、ショートカットを取っています。 xargsにこのリストを指定すると、個々のコマンドアトムを効果的に保護するNULLで分割するように指示されます。

2番目の例では、結果をfindの内部に保持しているため、各ファイルatomが何であるかを認識し、適切に処理することを保証するため、whoieビジネスを回避します。それらを引用する。

コマンドラインの最大サイズ?

この質問は時々出てくるので、おまけとして、この回答に追加します。主に、将来的に見つけられるようにするためです。 xargsを使用して、環境の制限を確認できます。

_$ xargs --show-limits
Your environment variables take up 4791 bytes
POSIX upper limit on argument length (this system): 2090313
POSIX smallest allowable upper limit on argument length (all systems): 4096
Maximum length of command we could actually use: 2085522
Size of command buffer we are actually using: 131072
_
12
slm
find . -name something.txt -exec myscript {} +

上記では、findは一致するすべてのファイル名を検索し、myscriptへの引数として提供します。これは、スペースやその他の奇妙な文字に関係なく、ファイル名で機能します。

すべてのファイル名が1行に収まる場合、myscriptは1回実行されます。シェルが処理するにはリストが長すぎる場合、findは必要に応じてmyscriptを複数回実行します。

MORE:コマンドラインにいくつのファイルが入りますか? man findは、findがコマンドラインを「xargsがビルドするのとほぼ同じ方法で」ビルドすると言っています。また、man xargsは、制限がシステムに依存していることと、xargs --show-limitsを実行して制限を決定できることを示しています。 (getconf ARG_MAXも可能です)。 Linuxでは、制限は 通常(ただし常にではない) コマンドラインあたり約200万文字です。

3
John1024

@slmの細かい答えにいくつかの追加。

引数のサイズの制限は、execve(2)システムコールにあります(実際には、引数と環境文字列およびポインターの累積サイズにあります)。 myscriptが、シェルが解釈できる言語で記述されている場合、多分executeする必要はありません。別のインタープリターを実行せずに、シェルにそれを解釈させるだけです。

次のようにスクリプトを実行すると、

_(. myscript x y)
_

それは次のようなものです:

_myscript x y
_

executing it(最終的にはexecutingsh(または、シーバン行で指定されているものがある場合)の代わりに)の代わりに、現在のシェルの子によって解釈されることを除いて、さらに多くの引数)。

_find -exec {} +_はシェルの組み込みコマンドであるため、_._コマンドで_._を使用することはできません。これは、findではなく、シェルで実行する必要があります。

zshを使用すると、簡単です。

_IFS=$'\0'
(. myscript $(find ... -print0))
_

または:

_(. myscript ${(ps:\0:)"$(find ... -print0)"}
_

zshを使用しても、ほとんどの機能がfindグロビングに組み込まれているため、最初はzshは必要ありません。

ただし、bash変数にはNUL文字を含めることができないため、別の方法を見つける必要があります。 1つの方法は次のとおりです。

_files=()
while IFS= read -rd '' -u3 file; do
  files+=("$file")
done 3< <(find ... -print0)
(. myscript "${files[@]}")
_

globstar 4.0以降では、bashオプションでzshスタイルの再帰的グロビングを使用することもできます。

_shopt -s globstar failglob dotglob
(. myscript ./**/something.txt)
_

_**_は、bash 4.3で修正されるまで、ディレクトリへのシンボリックリンクをたどったことに注意してください。また、bashにはzshグロビング修飾子が実装されていないため、findのすべての機能を利用できないことに注意してください。

別の代替方法は、GNU lsを使用することです。

_eval "files=(find ... -exec ls -d --quoting-style=Shell-always {} +)"
(. myscript "${files[@]}")
_

上記のメソッドは、myscriptexecutedが1回だけであることを確認する場合にも使用できます(引数リストが大きすぎる場合は失敗します)。最近のバージョンのLinuxでは、次のコマンドを使用して、引数リストの制限を引き上げ、さらには解除できます。

_ulimit -s 1048576
_

(1GiBスタックサイズ、その4分の1はarg + envリストに使用できます)。

_ulimit -s unlimited
_

(制限なし)

2

ほとんどのシステムでは、xargsまたは-exec command {} +を使用して、プログラムに渡されるコマンドラインの長さに制限があります。 man findから:

-exec command {} +
      This  variant  of the -exec action runs the specified command on
      the selected files, but the command line is built  by  appending
      each  selected file name at the end; the total number of invoca‐
      tions of the command will  be  much  less  than  the  number  of
      matched  files.   The command line is built in much the same way
      that xargs builds its command lines.  Only one instance of  `{}'
      is  allowed  within the command.  The command is executed in the
      starting directory.

呼び出しははるかに少なくなりますが、必ず1つになるとは限りません。あなたがすべきことは、コマンドライン引数-o -に基づいて可能である、スクリプト内のNUL区切りのファイル名をstdinから読み取ることです。私は次のようなことをします:

$ find . -name something.txt -print0 | myscript -0 -o -

オプションの引数をmyscriptに適宜実装します。

1
Timo

バックティック(または$(...))展開でスペースを保護する方法はありませんか?

いいえ、ありません。何故ですか?

Bashには、何を保護すべきか、何を保護すべきでないかを知る方法がありません。

UNIXファイル/パイプには配列がありません。それは単なるバイトストリームです。 _``_または$()内のコマンドは、ストリームを出力します。このストリームは、bashを飲み込み、単一の文字列として扱います。その時点では、2つの選択肢しかありません。引用符で囲むか、1つの文字列として保持するか、裸のままにして、bashが構成された動作に従って分割するようにします。

したがって、配列が必要な場合に必要なのは、配列を持つバイト形式を定義することであり、それがxargsfindのようなツールです:_-0_引数は、要素をnullバイトで終了するバイナリ配列形式に従って機能し、それ以外の場合は不透明なバイトストリームにセマンティクスを追加します。

残念ながら、bashはnullバイトで文字列を分割するように構成できません。 zshが可能なことを示してくれた https://unix.stackexchange.com/a/110108/1798 に感謝します。

xargs

コマンドを1回実行する必要があり、_xargs -0 -n 10000_で問題が解決すると述べました。そうではありません。10000を超えるパラメーターがある場合、コマンドは複数回実行されます。

厳密に1回実行するか失敗させる場合は、_-x_引数と、_-n_引数よりも大きい_-s_引数を指定する必要があります(実際:束全体に十分な大きさ)長さゼロの引数とコマンドの名前が_-s_サイズに収まりません)。 ( man xargs 、以下の抜粋を参照)

私が現在使用しているシステムのスタックは約8Mに制限されているため、これが私の制限です。

_$ printf '%s\0' -- {1..1302582} | xargs -x0n 2076858 -s 2076858 /bin/true
xargs: argument list too long
$ printf '%s\0' -- {1..1302581} | xargs -x0n 2076858 -s 2076858 /bin/true
(no output)
_

bash

外部コマンドを使用したくない場合は、配列にフィードするwhile-readループ( https://unix.stackexchange.com/a/110108/1798 )が唯一のループですbashがnullバイトで物事を分割する方法。

スタックサイズの制限を回避するためにスクリプト_( . ... "$@" )_を利用するというアイデアはすばらしいです(私は試してみましたが、うまくいきました)。しかし、通常の状況ではおそらく重要ではありません。

プロセスパイプに特別なfdを使用することは、stdinから何かを読み取りたい場合に重要ですが、それ以外の場合は必要ありません。

だから、日常の家庭のニーズのための最も簡単な「ネイティブ」な方法:

_files=()
while IFS= read -rd '' file; do
    files+=("$file")
done <(find ... -print0)

myscriptornonscript "${files[@]}"
_

プロセスツリーがきれいで見栄えが良い場合は、このメソッドを使用すると_exec mynonscript "${files[@]}"_を実行できます。これにより、bashプロセスがメモリから削除され、呼び出されたコマンドに置き換えられます。 xargsは、コマンドが一度しか実行されない場合でも、呼び出されたコマンドの実行中は常にメモリに残ります。


ネイティブのbashメソッドに反するものはこれです:

_$ time { printf '%s\0' -- {1..1302581} | xargs -x0n 2076858 -s 2076858 /bin/true; }

real    0m2.014s
user    0m2.008s
sys     0m0.172s

$ time {
  args=()
  while IFS= read -rd '' arg; do
    args+=( "$arg" )
  done < <(printf '%s\0' -- $(echo {1..1302581}))
  /bin/true "${args[@]}"
}
bash: /bin/true: Argument list too long

real    107m51.876s
user    107m38.532s
sys     0m7.940s
_

bashは配列処理用に最適化されていません。


man xargs

-n max-args

コマンドラインごとに最大max-args個の引数を使用します。 -xオプションを指定しない限り、サイズ(-sオプションを参照)を超えると、max-argsより少ない引数が使用されます。この場合、xargsは終了します。

-s max-chars

コマンドと最初の引数、および引数文字列の末尾の終端のnullを含め、コマンドラインごとに最大max-chars文字を使用します。最大許容値はシステムに依存し、execの引数の長さの制限として計算され、環境のサイズから2048バイトのヘッドルームを差し引いたものになります。この値が128KiBを超える場合、128Kibがデフォルト値として使用されます。それ以外の場合、デフォルト値は最大です。 1KiBは1024バイトです。

-バツ

サイズ(-sオプションを参照)を超えた場合は終了します。

0
clacke