find -execから引数にリテラル{}を渡す[重複]

find -execから引数にリテラル{}を渡す[重複]

for次のようなネストされたループを含むStack Overflowの質問に対する回答を作成しています。

for jpeg in **/foo.jpg; do
    d=${f%/foo.jpg}   # Directory containing foo.jpg
    for m4a in "$d"/*.m4a; do
        someCommand "$m4a" --arg "$jpeg" 
    done
done

find私は以下を使ってすべてをコマンドに変えるアイデアを持っていました。その他 find次のような基本的なコマンド-exec:

find . -type d -exec test -f {}/foo.jpg \;
               -exec find {} -name '*.m4a' \
                             -exec someCommand ??? --args {}/foo.jpg \;

問題は???{}find-execfind{}/foo.jpg

POSIX標準は、{}リテラルをコマンドの引数として渡すことについて実際には何も言いません。ただ言う

Utility_nameまたは引数文字列に「{}」2文字の代わりに「{}」2文字が含まれている場合、findがその2文字を置き換えるか、変更されていない文字列を使用するかは実装によって定義されます。

これは関連するある種のトリックを排除するようですsh -c

-exec sh -c 'someCommand {} --args "$1"' _ {}/foo.jpg \;

なぜなら、{}外部から交換することもそうでないかもしれませんfind

findそれでは、このように巣を建てる方法はありませんか?

ベストアンサー1

検索にはエスケープメカニズムはありません。

{}この事実は、既存および-exec/またはオプションの挿入を許可しません-execdir。交換はプレーンを使用して行われますstrncpy()。 /はGNU-find(参考execとして使用)execdir内に渡されますbc_push_arg{} +私たちが持っているテーブルの場合:

  /* "+" terminator, so we can just append our arguments after the
   * command and initial arguments.
   */
  execp->replace_vec = NULL;
  execp->ctl.replace_pat = NULL;
  execp->ctl.rplen = 0;
  execp->ctl.lines_per_exec = 0; /* no limit */
  execp->ctl.args_per_exec = 0; /* no limit */

  /* remember how many arguments there are */
  execp->ctl.initial_argc = (end-start) - 1;

  /* execp->state = xmalloc(sizeof struct buildcmd_state); */
  bc_init_state (&execp->ctl, &execp->state, execp);

  /* Gather the initial arguments.  Skip the {}. */
  for (i=start; i<end-1; ++i)
{
  bc_push_arg (&execp->ctl, &execp->state,
           argv[i], strlen (argv[i])+1,
           NULL, 0,
           1);
}

{}フォームに複数のインスタンスを持つことはできないため、すべてを最後に追加します{} +。私たちには{} ;次のものがあります。

  /* Semicolon terminator - more than one {} is supported, so we
   * have to do brace-replacement.
   */
  execp->num_args = end - start;

  execp->ctl.replace_pat = "{}";
  execp->ctl.rplen = strlen (execp->ctl.replace_pat);
  execp->ctl.lines_per_exec = 0; /* no limit */
  execp->ctl.args_per_exec = 0; /* no limit */
  execp->replace_vec = xmalloc (sizeof(char*)*execp->num_args);


  /* execp->state = xmalloc(sizeof(*(execp->state))); */
  bc_init_state (&execp->ctl, &execp->state, execp);

  /* Remember the (pre-replacement) arguments for later. */
  for (i=0; i<execp->num_args; ++i)
{
  execp->replace_vec[i] = argv[i+start];
}

だから私たちはexecp->ctl.replace_pat = "{}";。すべてそこにいるparser.c

上記の式を次のように変更します。

  size_t len;               /* Length in ARG before `replace_pat'.  */
  char *s = mbsstr (arg, ctl->replace_pat);
  if (s)
    {
      len = s - arg;
    }
  else
    {
      len = arglen;
    }

  if (bytes_left <= len)
    break;
  else
bytes_left -= len;

  strncpy (p, arg, len);
  p += len;
  arg += len;
  arglen -= len;

bc_do_insert()からbuildcmd.c

だから脱出口はありません{}。ただし、一部の find バージョンは置き換えられず、独自{}/fooにのみ置き換えられる{}ため、2 つの異なるバージョンの find を組み合わせることができます-exec sh -c 'someCommad {}'

gfindGNU-findとAIX findを想定すると、afind次のことが可能になります。

afind . -type d -execdir test -f foo.jpg \
        -exec sh -c 'gfind . -name "*.m4a" -exec someCommand {} \;' \;

しかし、それはひどいハッキングになります。

良い解決策

あなたが直面している問題は、ディレクトリ内の特定の種類のすべてのファイルを取得するためにグローバル検索を実行していることです。つまり、まずディレクトリを見つけてから、そのディレクトリ内のすべてのファイルをワイルドカードとして指定して、ディレクトリの一部にします。単一のコマンドライン

-execdirこれが解決すべき問題です。見つかったファイルを含むディレクトリからコマンドを実行します。たとえば、

$ mkdir -p a/a b/b c/c d e
$ touch a/a/foo.m4a b/b/foo.m4a
$ touch a/a/bar.m4a b/b/bar.m4a c/c/foobar.m4a
$ touch a/a/yay.jpg c/c/yay.jpg
$ find . -type f -name '*.m4a' -execdir test -e yay.jpg \; \
                               -execdir echo someCommand --arg yay.jpg {} +
someCommand --arg yay.jpg ./foobar.m4a
someCommand --arg yay.jpg ./bar.m4a ./foo.m4a

また、formの{} +代わりにformを使用しています。これにより、見つかったすべてのファイル(実行されたディレクトリにあります)が同じコマンドラインに配置されます。{} ;exec

コマンドがブロックされたb/bため実行されませんでした。test -e

おすすめ記事