Bash: cotação na substituição do comando

2

Em suma, quero usar diretórios listados por um comando em um comando find :

find $(produces_dir_names --options...) -find-options...

O problema vem com espaço em branco nos nomes dos diretórios. Eu pensei em citá-los na saída do comando de produção (que eu posso mudar) seria suficiente:

"a" "a b" "a b c"

mas o bash reclama:

find: ‘"a"’: No such file or directory
find: ‘"a’: No such file or directory
find: ‘b"’: No such file or directory
find: ‘"a’: No such file or directory
find: ‘b’: No such file or directory
find: ‘c"’: No such file or directory

Como você pode, veja bash dividirá a saída do comando nos espaços, mesmo com as aspas. Eu tentei mexer com IFS e defini-lo como \n , mas minha compreensão parece limitada demais para que funcione.

A única solução que encontrei foi nesta pergunta do Stack Overflow: substituição do comando bash remove quotation , ou seja, colocar um eval na frente dele, mas isso parece meio feio.

Minhas perguntas:

Existe uma maneira fácil e como seria escrever essa substituição, sem o eval ?

As citações são mesmo necessárias?

Exemplo (produzindo a mesma saída):

find $(echo '"a" "a b" "a b c"')
    
por ness 24.10.2016 / 19:28

3 respostas

2

Talvez em duas linhas

IFS=$'\n' DIRS=( $(produces_dir_names --options...) ) 
find "${DIRS[@]}" -find-options...

Exemplo:

$ mkdir -p "/tmp/test/a b/foo" "/tmp/test/x y/bar"

$ IFS=$'\n' DIRS=( $(printf "/tmp/test/a b\n/tmp/test/x y\n") )
$ find "${DIRS[@]}" -mindepth 1
/tmp/test/a b/foo
/tmp/test/x y/bar

Mas, em geral, esse não é um bom estilo. Por exemplo, você estará em apuros se o seu DIRS contiver novas linhas. Melhor consertar seu "generate_dir_names" para imprimir seqüências de caracteres terminadas por byte nulo. Quanto ao meu exemplo, seria algo como:

$ printf "/tmp/test/a b
produces_dir_names --options... | tr '\n' '
IFS=$'\n' DIRS=( $(produces_dir_names --options...) ) 
find "${DIRS[@]}" -find-options...
' | xargs -0 -I '{}' find '{}' -find-options...
/tmp/test/x y
$ mkdir -p "/tmp/test/a b/foo" "/tmp/test/x y/bar"

$ IFS=$'\n' DIRS=( $(printf "/tmp/test/a b\n/tmp/test/x y\n") )
$ find "${DIRS[@]}" -mindepth 1
/tmp/test/a b/foo
/tmp/test/x y/bar
" | xargs -0 -I '{}' find '{}' -mindepth 1 /tmp/test/a b/foo /tmp/test/x y/bar

Se você não pode corrigir "produces_dir_names", em relação ao meu último comentário, a solução mais geral ficaria assim:

$ printf "/tmp/test/a b
produces_dir_names --options... | tr '\n' '%pre%' | xargs -0  -I '{}' find '{}' -find-options...
/tmp/test/x y%pre%" | xargs -0 -I '{}' find '{}' -mindepth 1 /tmp/test/a b/foo /tmp/test/x y/bar

Ainda há problemas com "novas linhas", a menos que você corrija "creates_dir_names" para evitar tr .

    
por 24.10.2016 / 20:05
1

a resposta do rudimeier é boa - especificamente, a parte sobre como modificar produces_dir_names imprimir seqüências terminadas com nulo - mas pode não ser óbvio pela sua resposta que executa find uma vez para cada diretório. Se isso é bom o suficiente, do que bem. Mas, claro, é possível invocar find com vários pontos de partida; por exemplo,

find  dir1 dir2 dir3  -find-options...

e surge da pergunta que é isso que você quer. Isso pode ser feito da seguinte maneira:

printf "a
find  dir1 dir2 dir3  -find-options...
a b
printf "a%pre%a b%pre%a b c" | xargs -0 sh -c 'find "$@" -find-options...' sh
a b c" | xargs -0 sh -c 'find "$@" -find-options...' sh

Isso faz com que xargs invoque sh -c uma vez com todos os nomes de diretório anexados ao comando. O shell expandirá "$@" para uma lista desses nomes de diretório.

P.S. Se produces_dir_names listar muitos nomes de diretórios colocar em uma linha de comando, então xargs será forçado a gerar alguns comandos. Use xargs --verbose para ver quais comandos xargs está gerando.

    
por 24.10.2016 / 22:00
0

Apenas para esclarecer o mistério das mensagens de erro que você está recebendo:

find: ‘"a"’: No such file or directory
find: ‘"a’: No such file or directory
find: ‘b"’: No such file or directory
find: ‘"a’: No such file or directory
find: ‘b’: No such file or directory
find: ‘c"’: No such file or directory

A resposta é que a remoção de citações do Bash não remove as citações que resultaram da substituição de comandos.

De LESS='+/^ *Quote Removal' man bash

Quote Removal
    After the preceding expansions, all unquoted occurrences of the charac-
    ters  \,  ', and " that did not result from one of the above expansions
    are removed.

As "expansões precedentes" mencionadas incluem:

EXPANSION
   Brace Expansion
   Tilde Expansion
   Parameter Expansion
   Command Substitution
   Arithmetic Expansion
   Process Substitution
   Word Splitting
   Pathname Expansion
   Quote Removal
    
por 25.10.2016 / 01:14