Bash array com espaços em elementos

150

Estou tentando construir uma matriz em bash dos nomes de arquivos da minha câmera:

FILES=(2011-09-04 21.43.02.jpg
2011-09-05 10.23.14.jpg
2011-09-09 12.31.16.jpg
2011-09-11 08.43.12.jpg)

Como você pode ver, há um espaço no meio de cada nome de arquivo.

Tentei colocar cada nome entre aspas e escapar do espaço com uma barra invertida, nenhuma das quais funciona.

Quando tento acessar os elementos da matriz, ele continua tratando o espaço como o elemento delimitador.

Como capturar corretamente os nomes de arquivos com um espaço dentro do nome?

abelenky
fonte
Você já tentou adicionar os arquivos da maneira antiga? Gosta FILES[0] = ...? (Edit: Acabei de fazer; não funciona. Interessante).
Dan Fego 31/01
POSIX: stackoverflow.com/questions/2936922/…
Ciro Santilli escreveu:
Todas as respostas aqui são detalhadas para mim usando o Cygwin. Faz coisas estranhas se houver espaços nos nomes dos arquivos, ponto final. Eu resolvo isso criando um "array" em um arquivo de texto com uma lista de todos os elementos com os quais quero trabalhar e iterando sobre as linhas do arquivo: A formatação está mexendo com os backticks pretendidos aqui envolvendo o comando entre parênteses: IFS = ""; matriz = ( find . -maxdepth 1 -type f -iname \*.$1 -printf '%f\n'); para o elemento em $ {array [@]}; faça eco do elemento $; feito
Alex Hall

Respostas:

121

Acho que o problema pode estar em parte na maneira como você está acessando os elementos. Se eu fizer um simples for elem in $FILES, encontro o mesmo problema que você. No entanto, se eu acessar a matriz por meio de seus índices, assim funcionará se eu adicionar os elementos numericamente ou com escapes:

for ((i = 0; i < ${#FILES[@]}; i++))
do
    echo "${FILES[$i]}"
done

Qualquer uma dessas declarações de $FILESdeve funcionar:

FILES=(2011-09-04\ 21.43.02.jpg
2011-09-05\ 10.23.14.jpg
2011-09-09\ 12.31.16.jpg
2011-09-11\ 08.43.12.jpg)

ou

FILES=("2011-09-04 21.43.02.jpg"
"2011-09-05 10.23.14.jpg"
"2011-09-09 12.31.16.jpg"
"2011-09-11 08.43.12.jpg")

ou

FILES[0]="2011-09-04 21.43.02.jpg"
FILES[1]="2011-09-05 10.23.14.jpg"
FILES[2]="2011-09-09 12.31.16.jpg"
FILES[3]="2011-09-11 08.43.12.jpg"
Dan Fego
fonte
6
Observe que você deve usar aspas duplas ao usar os elementos da matriz (por exemplo echo "${FILES[$i]}"). Não importa echo, mas será útil para qualquer coisa que o use como nome de arquivo.
Gordon Davisson
26
Não é necessário fazer um loop sobre os índices quando você pode fazer um loop sobre os elementos com for f in "${FILES[@]}".
Mark2
10
@ MarkEdgar, estou enfrentando problemas com for f em $ {FILES [@]} quando os membros da matriz têm espaços. Parece que toda a matriz é reinterpretada novamente, com os espaços dividindo seus membros existentes em dois ou mais elementos. Parece que o "" são muito importantes
Michael Shaw
1
O que o #símbolo sharp ( ) faz na for ((i = 0; i < ${#FILES[@]}; i++))declaração?
quer
4
Eu respondi isso há seis anos, mas acredito que é para obter a contagem do número de elementos no array FILES.
Dan Fego
91

Deve haver algo errado com a maneira como você acessa os itens da matriz. Veja como é feito:

for elem in "${files[@]}"
...

Na página de manual do bash :

Qualquer elemento de uma matriz pode ser referenciado usando $ {name [subscript]}. ... Se subscrito for @ ou *, a palavra se expandirá para todos os membros do nome. Esses subscritos diferem apenas quando a palavra aparece entre aspas duplas. Se a palavra estiver entre aspas duplas, $ {name [*]} será expandida para uma única palavra com o valor de cada membro da matriz separado pelo primeiro caractere da variável especial IFS e $ {name [@]} expandirá cada elemento de nome para uma palavra separada .

Obviamente, você também deve usar aspas duplas ao acessar um único membro

cp "${files[0]}" /tmp
user123444555621
fonte
3
A solução mais limpa e elegante deste grupo, porém, deve reiterar que cada elemento definido na matriz deve ser citado.
Maverick
Embora a resposta de Dan Fego seja eficaz, essa é a maneira mais idiomática de lidar com espaços nos elementos.
Daniel Zhang
3
Vindo de outras linguagens de programação, a terminologia desse trecho é realmente difícil de entender. Além disso, a sintaxe é desconcertante. Ficaria extremamente grato se você pudesse entrar um pouco mais? Particularmenteexpands to a single word with the value of each array member separated by the first character of the IFS special variable
CL22
1
Sim, concorda que as aspas duplas estão resolvendo o problema e isso é melhor do que outras soluções. Para explicar melhor - a maioria dos outros não possui aspas duplas. Você acertou: for elem in "${files[@]}"enquanto eles têm for elem in ${files[@]}- então os espaços confundem a expansão e as tentativas de execução nas palavras individuais.
Arntg
Isso não funciona para mim no macOS 10.14.4, que usa "GNU bash, versão 3.2.57 (1) -release (x86_64-apple-darwin18)". Talvez um bug na versão mais antiga do bash?
Mark Ribau
43

Você precisa usar o IFS para parar o espaço como delimitador de elemento.

FILES=("2011-09-04 21.43.02.jpg"
       "2011-09-05 10.23.14.jpg"
       "2011-09-09 12.31.16.jpg"
       "2011-09-11 08.43.12.jpg")
IFS=""
for jpg in ${FILES[*]}
do
    echo "${jpg}"
done

Se você deseja separar com base em. então faça IFS = "." Espero que ajude você :)

Khushneet
fonte
3
Eu tive que mover o IFS = "" para antes da atribuição da matriz, mas esta é a resposta correta.
22615 Rob
Estou usando várias matrizes para analisar informações e terei o efeito de IFS = "" trabalhar em apenas uma delas. Depois que eu uso o IFS = "", todas as outras matrizes param de analisar adequadamente. Alguma dica sobre isso?
Paulo Pedroso
Paulo, veja outra resposta aqui que pode ser melhor para o seu caso: stackoverflow.com/a/9089186/1041319 . Não tentei o IFS = "" e parece que o resolve de maneira elegante - mas seu exemplo mostra por que alguém pode encontrar problemas em alguns casos. Pode ser possível definir o IFS = "" em uma única linha, mas ainda pode ser mais confuso do que a outra solução.
Arntg 26/05
Também funcionou para mim no bash. Graças @Khushneet Eu estava procurando isso por meia hora ...
csonuryilmaz
Ótimo, apenas responda nesta página que funcionou. Mas eu também tive que mover o IFS="" antes da construção da matriz .
Pkamb # 24/18
13

Concordo com os outros que é provável que você esteja acessando os elementos que são o problema. A citação dos nomes de arquivo na atribuição da matriz está correta:

FILES=(
  "2011-09-04 21.43.02.jpg"
  "2011-09-05 10.23.14.jpg"
  "2011-09-09 12.31.16.jpg"
  "2011-09-11 08.43.12.jpg"
)

for f in "${FILES[@]}"
do
  echo "$f"
done

O uso de aspas duplas em torno de qualquer matriz do formulário "${FILES[@]}"divide a matriz em uma palavra por elemento da matriz. Não faz nenhuma divisão de palavras além disso.

Usar "${FILES[*]}"também tem um significado especial, mas une os elementos da matriz ao primeiro caractere de $ IFS, resultando em uma palavra, que provavelmente não é o que você deseja.

Usando um valor simples ${array[@]}ou ${array[*]}assuntos, o resultado dessa expansão para uma maior divisão de palavras, resultará em palavras divididas em espaços (e qualquer outra coisa dentro $IFS) em vez de uma palavra por elemento da matriz.

Usar um loop for no estilo C também é bom e evita a preocupação com a divisão de palavras, se você não estiver claro:

for (( i = 0; i < ${#FILES[@]}; i++ ))
do
  echo "${FILES[$i]}"
done
Dean Hall
fonte
3

Escapar funciona.

#!/bin/bash

FILES=(2011-09-04\ 21.43.02.jpg
2011-09-05\ 10.23.14.jpg
2011-09-09\ 12.31.16.jpg
2011-09-11\ 08.43.12.jpg)

echo ${FILES[0]}
echo ${FILES[1]}
echo ${FILES[2]}
echo ${FILES[3]}

Resultado:

$ ./test.sh
2011-09-04 21.43.02.jpg
2011-09-05 10.23.14.jpg
2011-09-09 12.31.16.jpg
2011-09-11 08.43.12.jpg

Citar as strings também produz a mesma saída.

Chris Seymour
fonte
3

Se você tivesse seu array assim: #! / Bin / bash

Unix[0]='Debian'
Unix[1]="Red Hat"
Unix[2]='Ubuntu'
Unix[3]='Suse'

for i in $(echo ${Unix[@]});
    do echo $i;
done

Você obteria:

Debian
Red
Hat
Ubuntu
Suse

Não sei por que, mas o loop divide os espaços e os coloca como um item individual, até você o envolve com aspas.

Para contornar isso, em vez de chamar os elementos da matriz, você chama os índices, que recebem a cadeia completa envolvida entre aspas. Deve estar entre aspas!

#!/bin/bash

Unix[0]='Debian'
Unix[1]='Red Hat'
Unix[2]='Ubuntu'
Unix[3]='Suse'

for i in $(echo ${!Unix[@]});
    do echo ${Unix[$i]};
done

Então você terá:

Debian
Red Hat
Ubuntu
Suse
Jonni2016aa
fonte
2

Não é exatamente uma resposta para o problema de citação / escape da pergunta original, mas provavelmente algo que realmente teria sido mais útil para o op:

unset FILES
for f in 2011-*.jpg; do FILES+=("$f"); done
echo "${FILES[@]}"

Onde é claro que a expressão teria que ser adotada para o requisito específico (por exemplo, *.jpgpara todas ou 2001-09-11*.jpgapenas as figuras de um determinado dia).

TNT
fonte
0

Outra solução é usar um loop "while" em vez de um loop "for":

index=0
while [ ${index} -lt ${#Array[@]} ]
  do
     echo ${Array[${index}]}
     index=$(( $index + 1 ))
  done
Javier Salas
fonte
0

Se você não está preso ao uso bash, o manuseio diferente dos espaços nos nomes dos arquivos é um dos benefícios da casca do peixe . Considere um diretório que contém dois arquivos: "a b.txt" e "b c.txt". Aqui está um palpite razoável ao processar uma lista de arquivos gerados a partir de outro comando bash, mas falha devido aos espaços nos nomes dos arquivos que você encontrou:

# bash
$ for f in $(ls *.txt); { echo $f; }
a
b.txt
b
c.txt

Com fish, a sintaxe é quase idêntica, mas o resultado é o que você esperaria:

# fish
for f in (ls *.txt); echo $f; end
a b.txt
b c.txt

Funciona de maneira diferente porque o peixe divide a saída dos comandos em novas linhas, não em espaços.

Se você tem um caso em que deseja dividir espaços em vez de novas linhas, fishhá uma sintaxe muito legível para isso:

for f in (ls *.txt | string split " "); echo $f; end
Mark Stosberg
fonte
0

Eu costumava redefinir o valor e a reversão do IFS quando concluído.

# backup IFS value
O_IFS=$IFS

# reset IFS value
IFS=""

FILES=(
"2011-09-04 21.43.02.jpg"
"2011-09-05 10.23.14.jpg"
"2011-09-09 12.31.16.jpg"
"2011-09-11 08.43.12.jpg"
)

for file in ${FILES[@]}; do
    echo ${file}
done

# rollback IFS value
IFS=${O_IFS}

Possível saída do loop:

2011-09-04 21.43.02.jpg

2011-09-05 10.23.14.jpg

2011-09-09 12.31.16.jpg

2011-09-11 08.43.12.jpg

Madan Sapkota
fonte