Passar literal {} como um argumento em find -exec [duplicate]

3

Eu estava escrevendo uma resposta para uma pergunta no Stack Overflow, e a resposta envolveu um loop for aninhado semelhante a

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

Eu tive a ideia de transformar a coisa toda em um único comando find que usou o outro find no seu comando -exec primary, algo como:

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

O problema é com o ??? . Eu gostaria de passar um literal {} para ser usado como um argumento no aninhado find -exec sem o exterior find substituindo-o por um nome de diretório como seria com {}/foo.jpg .

O padrão POSIX realmente não diz nada sobre passar um literal {} como um argumento para um comando. Apenas diz

If a utility_name or argument string contains the two characters "{}", but not just the two characters "{}", it is implementation-defined whether find replaces those two characters or uses the string without change.

Isso parece impedir algum tipo de truque envolvendo sh -c , como

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

desde que {} pode ou não ser substituído pelo% externofind.

Então, existe alguma maneira de aninhar find dessa maneira?

    
por chepner 12.02.2017 / 03:47

1 resposta

1

O Find não possui nenhum mecanismo de escape

Este fato não permitirá que você insira {} dentro de uma opção -exec e / ou -execdir existente. A substituição é feita com strncpy() simples. O exec / execdir é executado dentro do GNU-find (que eu estou usando como referência) através de bc_push_arg . Para o formulário {} + , temos:

  /* "+" 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);
}

Ele acrescenta tudo no final, pois você não pode ter mais de uma instância de {} no formulário {} + . E para o {} ; , temos:

  /* 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];
}

Portanto, temos execp->ctl.replace_pat = "{}"; . Isso é tudo em parser.c

O texto acima é substituído como:

  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;

Em bc_do_insert() em buildcmd.c .

Portanto, não, não há como escapar do {} . No entanto, algumas versões do find não substituirão {}/foo , mas apenas {} , assim você poderá usar duas versões diferentes de find juntas com um -exec sh -c 'someCommad {}' .

Assumindo que gfind é o GNU-find e afind é o AIX que você provavelmente consegue:

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

Mas isso seria um hack horrível.

Solução alternativa decente

O problema que você está enfrentando é que você está executando a globalização para obter todos os arquivos de um tipo em um diretório. Em outras palavras, você está primeiro encontrando um diretório e globbing todos os arquivos desse diretório para fazer parte de uma linha de comando única .

Este é o tipo de problema que -execdir pretende resolver. Ele irá executar o comando no diretório que contém o arquivo encontrado. Por exemplo:

$ 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

Além disso, estou usando o formulário {} + em vez do formulário {} ; para o exec . Isso colocará todos os arquivos encontrados (no diretório em que está sendo executado) na mesma linha de comando.

Observe que o comando não foi executado em b/b porque o test -e o impediu.

    
por 12.02.2017 / 04:58

Tags