Como lidar com espaços em uma variável

5

Estou trabalhando em alguns scripts:

for x in 'find ./ -name *.pdf'
do
  echo pathname $x
done

Meus nomes de arquivos são Test1 ( Volume II) , Test2 ( Volume II) . Estou recebendo um retorno de

pathname Test1
pathname (
pathname Volume
pathname II
…

Como faço para que ele fique como uma variável?

    
por Duy 13.06.2013 / 20:11

5 respostas

7

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 '%szsh' {} + com o mesmo resultado. NUL é o único caractere que não pode ocorrer em um nome de arquivo.

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='
'
    
por 13.06.2013 / 21:54
3

A maneira mais segura é usar globbing:

for file in *pdf; do echo pathname "$file"; done

Se você precisar encontrar todos os PDFs recursivamente, faça o seguinte:

shopt -s globstar
for file in **/*pdf; do echo pathname "$file"; done
    
por 13.06.2013 / 20:18
3

Existem algumas opções. Continuando com uma solução de looping, você pode usar read para ler uma linha de cada vez em uma variável:

find . -name "*.pdf" | while IFS= read -r x
do
  echo pathname "$x"
done

Você também pode usar xargs -0 e find -print0 para manipular qualquer tipo de caracteres especiais:

find . -name "*.pdf" -print0 | xargs -0 -L1 echo pathname
    
por 13.06.2013 / 20:23
3

Tente isso em uma linha:

find ./ -name "*.pdf" -exec echo pathname {} \;
    
por 13.06.2013 / 20:22
2

Depois de procurar algumas horas para encontrar uma solução para o shell padrão do FreeBSD (/ bin / sh), finalmente encontrei minha própria solução. Envolve o uso de sed para destruir / criar espaços em branco. Isso também permitirá que você altere variáveis dentro de seu loop e as use externamente, ao contrário de usar um loop while (enquanto loops geram subshells em / bin / sh).

REMOTE_FOLDER=/media/images
FILE_LIST=$(find $REMOTE_FOLDER | sed 's/ /\/\/\/\//g')
for FILE in $FILE_LIST; do
    FILE=$(echo $FILE | sed 's/\/\/\/\// /g')
    # do whatever you need to do with $FILE
done
    
por 10.03.2017 / 05:46