Bash: aspas escapadas na subshell [duplicata]

3

Quando eu executo o seguinte comando:

#!/bin/bash
while IFS= read -r -d '' file; do
    files+=$file
done < <(find -type f -name '*.c' -print0)
echo "${files[@]}"

Eu não obtenho o mesmo resultado que este:

#!/bin/bash
find_args="-type f '*.c' -print0"
while IFS= read -r -d '' file; do
    files+=$file
done < <(find $find_args)
echo "${files[@]}"

Como posso consertar o segundo cenário como equivalente ao primeiro?

Meu entendimento é que, como há aspas simples nas aspas duplas, as aspas simples ficam com escape, o que produz uma expansão ruim que se parece com algo assim:

find -type f -name ''\''*.c'\'' -print0
    
por Rogozhin 28.07.2017 / 05:42

4 respostas

3

(Note que você tem um erro de digitação. Você parou o sinal -name no segundo exemplo.)

Uma abordagem é colocar os argumentos em uma matriz e passar a matriz apropriadamente para find ...

#!/bin/bash
find_args=(-type f -name '*.c' -print0)
while IFS= read -r -d '' file; do
    files+=$file
done < <(find "${find_args[@]}")
echo "${files[@]}"

O formato ${foo[@]} se expande para todos os elementos da matriz, cada qual uma palavra individual (em vez de se expandir para uma única string). Isso está mais próximo da intenção do script original.

    
por 28.07.2017 / 06:02
4

A resposta da BLay está correta, mas para desconstruir o que está realmente acontecendo aqui (ignorando o erro de digitação do -name primária):

#!/bin/bash
while IFS= read -r -d '' file; do
    files+=$file
done < <(find -type f -name '*.c' -print0)
echo "${files[@]}"

No shell iniciado pela substituição do processo ( <(...) ), o seguinte comando é analisado por bash:

find -type f -name '*.c' -print0

Como o glob *.c é citado, o bash não o o expande. No entanto, as aspas simples são removidas. Portanto, quando o processo find for iniciado, o que ele vê como sua lista de argumentos é:

-type
f
-name
*.c
-print0

Observe que esses argumentos são separados por bytes nulos , não por espaços ou novas linhas. Isso está no nível C, não no nível do shell. Isso tem a ver com a forma como os programas são executados usando execve() em C.

Agora, para contrastar, no seguinte snippet:

#!/bin/bash
find_args="-type f -name '*.c' -print0"
while IFS= read -r -d '' file; do
    files+=$file
done < <(find $find_args)
echo "${files[@]}"

O valor da variável find_args está definido como:

-type f -name '*.c' -print0

(As aspas duplas não fazem parte do valor, mas os caracteres de aspas simples são . )

Quando o comando find $find_args é executado, conforme man bash , o token $find_args está sujeito à expansão de parâmetro seguido por divisão de palavras seguida por expansão do nome do caminho (também conhecido como expansão glob).

Após a expansão do parâmetro, você tem -type f -name '*.c' -print0 . Note que isso é após remoção de cotação. Assim, as aspas simples não serão removidas.

Após a divisão de palavras, você tem as seguintes palavras separadas:

-type
f
-name
'*.c'
-print0

Então vem a expansão do nome do caminho. É claro que '*.c' provavelmente não corresponderá a nada, já que você normalmente não coloca aspas nos seus nomes de arquivo, então o resultado provável será que '*.c' será passado como um padrão literal para find e, portanto, o -name primary falhará em todos os arquivos. (Teria sucesso apenas se houver um arquivo cujo nome comece com uma aspa simples e termine com os três caracteres .c' )

Editar: Na verdade, se houver um arquivo desse tipo, o glob '*.c' será expandido para corresponder a esse arquivo e qualquer outro desses arquivos e então a expansão [o nome do arquivo real] será passada para find como um padrão . Então, se o -print0 primário será alcançado ou não, depende de (a) se existe apenas um nome de arquivo, e ( b) se esse nome de arquivo, interpretado como glob, corresponde a si mesmo.

Exemplos:

Se você executar touch "'something.c'" , o glob '*.c' expandirá para 'something.c' e, em seguida, o find primary -name 'something.c' corresponderá a esse arquivo também e será impresso.

Se você executar touch "'namewithcharset[a].c'" , o glob '*.c' será expandido para esse pelo shell, mas o find primary -name 'namewithcharset[a].c' não será não correspondente - corresponderia apenas a 'namewithcharseta.c' , que não existe, por isso -print0 não seria alcançado.

Se você executar touch "'x.c'" "'y.c'" , o glob '*.c' será expandido para ambos nomes de arquivos, o que causará um erro na saída de find porque 'y.c' não é um primário válido (e não pode ser como não começa com um hífen).

Se a opção nullglob estiver definida, você terá um comportamento diferente.

Veja também:

por 28.07.2017 / 06:33
1

Além do que já foi dito, você precisa:

  • declare a variável $files como uma matriz, pois por padrão ela será escalar e var+=something em um escalar faz a concatenação de cadeias (ou adição aritmética se o escalar tiver recebido o atributo número inteiro ) . Ou use a sintaxe var+=(something) (que converteria automaticamente a variável em uma matriz).
  • inicialize a variável (como não definida ou para uma lista vazia), caso contrário, você poderá herdar um valor inicial do ambiente.

Fazendo:

files=()
while ...
  files+=$file # or files+=("$file")
done

Seria suficiente, a menos que a variável files tenha sido anteriormente declarada como uma matriz associativa no início do script (nesse caso, files+=something seria como files["0"]+=something e files+=("$files") seria um erro).

Se você não pode garantir que files não tenha sido definido como um array associativo no início do script, talvez seja necessário:

typeset -a files=()

em vez disso, embora isso tenha o efeito colateral de limitar o escopo da variável à função delimitadora. typeset -ga files=() não funciona corretamente como uma solução alternativa para isso em bash , pois declararia a variável no escopo global. unset files; files=() pode não funcionar como unset files em alguns casos pode revelar a variável files de um escopo externo (que pode ser uma matriz associativa) em vez de desativá-la.

    
por 28.07.2017 / 11:53
0

Você precisa fazer algumas alterações no segundo script. Insira o -name ausente, remova as aspas simples e use set -f para evitar a expansão do asterisco. No início do script ou (como mostrado) no subshell:

<(set -f; find $find_args)

mude o eco para printf e faça a atribuição como files+=("$file") para realmente ter uma matriz de valores. O que você tem apenas constrói uma string.

#!/bin/bash
find_args="-type f -name *.c -print0"
while IFS= read -r -d '' file; do
    files+=("$file")
done < <(set -f; find $find_args)
printf '<%s> ' "${files[@]}"; echo
    
por 28.07.2017 / 11:25