Como dito muitas vezes neste site, deixando uma expansão variável (como em $var
) ou substituição de comando (como em 'cmd'
ou $(cmd)
) (ou expansão aritmética (como em $((11 * 11))
) na maioria dos shells ) não citada em shells Bourne / POSIX é o operador split + glob.
O conteúdo de $var
ou a saída de cmd
(sem os caracteres de nova linha à direita) é dividido de acordo com o valor atual da variável $IFS
special (que por padrão contém os caracteres SPC, TAB e NL (e NUL em zsh
)) e cada palavra resultante dessa divisão está sujeita à geração de nome de arquivo, também conhecida como globbing .
Por exemplo, se find
ouputs ./foo bar.pdf\n./*foo*\tbar.pdf\n
( \t
significando TAB e \n
NL), com o valor padrão $IFS
, a substituição de comando será expandida para ./foo bar.pdf\n./*foo*\tbar.pdf
(nova linha final removida) e, em seguida, ser dividido em ./foo
, bar.pdf
, ./*foo*
e foo.pdf
e ./*foo*
, que é um padrão curinga, será expandido em tantos argumentos quanto houver arquivos não ocultos no diretório atual cuja nome contém foo
.
Se você quiser dividir somente os caracteres de nova linha, precisará definir $IFS
somente para nova linha:
IFS='
'
Se você não quiser que os padrões de caracteres curinga sejam expandidos, será necessário desativá-lo com
set -f
No entanto, observe que a nova linha é um caractere tão válido quanto qualquer outro em um nome de arquivo, portanto, de maneira mais geral, a saída find -print
não pode ser processada de forma confiável.
Uma saída como:
./a.pdf
./b.pdf
quer dizer os arquivos a.pdf
e b.pdf
no diretório atual, ou o arquivo chamado b.pdf
no diretório a.pdf\n.
.
Algumas implementações de find
como o GNU find
(de onde é originado) têm um predicado -print0
para gerar o nome do arquivo seguido por um caractere NUL em vez de um caractere NL. Com o padrão find
, você pode usar -exec printf '%s
com o mesmo resultado. NUL é o único caractere que não pode ocorrer em um nome de arquivo. zsh
' {} +
No entanto, $IFS
é o único shell que pode armazenar um caractere NUL em suas variáveis (como o caractere set -f
), portanto:
IFS=$'find . -name '*.pdf' -exec the command {} \;
'
for i in 'find ... -print0'; do
...
done
(não há necessidade de zsh
em zsh
, pois zsh
não faz globbing na substituição de comando) funcionará em find
, mas não em outras shells.
Melhor e, portably, é ter zsh
chamando os comandos que você deseja executar nesses arquivos. Como @Gnouc sugere:
find . -name '*.pdf' -exec sh -c '
for i do
something complex with "$i"
done' sh {} +
Se você precisar de algo mais complexo envolvendo declarações de shell, ainda é possível fazer coisas como:
find . -name '*.pdf' -print0 |
while IFS= read -r -d '' file; do
whatever with "$file"
done
Com bash
ou zsh
, você também pode fazer:
find . -name '*.pdf' -type f -exec ls -ld {} +
No entanto, observe que o stdin dentro do loop é afetado.
find
(desde 1990) tem a maior parte da funcionalidade (*/)#
incluída em seus recursos de globalização por meio de uma sintaxe na qual você pode especificar qualquer nível de subdiretórios ( **/
sintaxe ou sua forma mais simples -type f
) e globbing qualificadores (que são o pingente de -mtime
, -perm
, find
... em **/
).
A parte ksh93
disso foi copiada por fish
em 2003, bash
em 2005, tcsh
em 2009 e tcsh
em 2010 (embora ***/
também copiasse a parte bash
). E todos eles não permitem isso por padrão. Infelizmente, observe que fish
e **
-L
seguem os links simbólicos para os diretórios (como -follow
/ find
em ***
ou zsh
em tcsh
ou pdf
).
Nesses shells, você pode encontrar find
arquivos em qualquer nível de subdiretórios sem precisar depender de fish
, mas observe que a advertência está acima de bash
e zsh
, e apenas zsh
tem o globbing qualificadores.
Assim, por exemplo, o equivalente bash
de:
ls -ld ./**/*.pdf(D.)
seria:
shopt -s failglob
shopt -s globstart
files=(./**/*.pdf) &&
for i do
[ -f "$i" ] && ! [ -L "$i" ] && set -- "$i" "$@"
shift
done && ls -ld "$@"
Enquanto com %code% , você precisa fazer algo como:
IFS='
'