Matrice di Bash con spazi negli elementi


150

Sto cercando di costruire un array in bash dei nomi di file dalla mia fotocamera:

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)

Come puoi vedere, c'è uno spazio nel mezzo di ciascun nome file.

Ho provato a racchiudere ogni nome tra virgolette e a sfuggire allo spazio con una barra rovesciata, nessuna delle quali funziona.

Quando provo ad accedere agli elementi dell'array, continua a trattare lo spazio come l'elemento delimitatore.

Come posso catturare correttamente i nomi dei file con uno spazio all'interno del nome?


Hai provato ad aggiungere i file alla vecchia maniera? Come FILES[0] = ...? (Modifica: l'ho appena fatto; non funziona. Interessante).
Dan Fego,


Tutte le risposte qui vengono meno per me usando Cygwin. Fa cose strane se ci sono spazi nei nomi dei file, punto. Ci aggiro creando un "array" in un elenco di file di testo con tutti gli elementi con cui voglio lavorare e ripetendo le righe nel file: la formattazione si confonde con i backtick previsti che circondano il comando tra parentesi: IFS = ""; array = ( find . -maxdepth 1 -type f -iname \*.$1 -printf '%f\n'); per elemento in $ {array [@]}; fa eco $ element; fatto
Alex Hall

Risposte:


122

Penso che il problema potrebbe essere in parte dovuto al modo in cui accedi agli elementi. Se faccio un semplice for elem in $FILES, ho lo stesso problema con te. Tuttavia, se accedo all'array attraverso i suoi indici, in questo modo, funziona se aggiungo gli elementi in modo numerico o con escape:

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

Qualsiasi di queste dichiarazioni di $FILESdovrebbe funzionare:

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)

o

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")

o

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"

6
Si noti che è necessario utilizzare le virgolette doppie quando si utilizzano gli elementi dell'array (ad es echo "${FILES[$i]}".). Non importa echo, ma lo farà per tutto ciò che lo utilizza come nome file.
Gordon Davisson,

26
Non è necessario eseguire il ciclo sugli indici quando è possibile eseguire il ciclo degli elementi con for f in "${FILES[@]}".
Mark Edgar,

10
@MarkEdgar ho riscontrato problemi con for f in $ {FILES [@]} quando i membri dell'array dispongono di spazi. Sembra che l'intero array venga reinterpretato di nuovo, con gli spazi che sputano i membri esistenti in due o più elementi. Sembra che i "" siano molto importanti
Michael Shaw,

1
Cosa fa il #simbolo sharp ( ) for ((i = 0; i < ${#FILES[@]}; i++))nell'istruzione?
Michal Vician,

4
Ho risposto sei anni fa, ma credo che sia per ottenere il conteggio del numero di elementi nell'array FILES.
Dan Fego,

91

Deve esserci qualcosa di sbagliato nel modo in cui si accede agli elementi dell'array. Ecco come è fatto:

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

Dalla manpage di bash :

È possibile fare riferimento a qualsiasi elemento di un array usando $ {name [pedice]}. ... Se il pedice è @ o *, la parola si espande a tutti i membri del nome. Questi indici differiscono solo quando la parola appare tra virgolette. Se la parola è racchiusa tra virgolette, $ {name [*]} si espande in una singola parola con il valore di ciascun membro dell'array separato dal primo carattere della variabile speciale IFS e $ {name [@]} espande ogni elemento di nome di una parola separata .

Naturalmente, dovresti anche usare le virgolette doppie quando accedi a un singolo membro

cp "${files[0]}" /tmp

3
La soluzione più pulita ed elegante in questo gruppo, sebbene dovrebbe ripetere che ogni elemento definito nell'array dovrebbe essere citato.
Maverick,

Mentre la risposta di Dan Fego è efficace, questo è il modo più idiomatico di gestire gli spazi negli elementi.
Daniel Zhang,

3
Proveniente da altri linguaggi di programmazione, la terminologia di questo estratto è davvero difficile da capire. Inoltre la sintassi è sconcertante. Le sarei estremamente grato se potessi approfondire un po 'di più? In particolareexpands to a single word with the value of each array member separated by the first character of the IFS special variable
CL22,

1
Sì, d'accordo che le doppie virgolette lo stanno risolvendo e questo è meglio di altre soluzioni. Per spiegare ulteriormente - la maggior parte degli altri mancano solo delle doppie virgolette. Hai ottenuto il giusto:, for elem in "${files[@]}"mentre hanno for elem in ${files[@]}- così gli spazi confondono l'espansione e per i tentativi di correre sulle singole parole.
arntg,

Questo non funziona per me in macOS 10.14.4, che utilizza "GNU bash, versione 3.2.57 (1) -release (x86_64-apple-darwin18)". Forse un bug nella versione precedente di bash?
Mark Ribau,

43

È necessario utilizzare IFS per interrompere lo spazio come delimitatore di elementi.

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 si desidera separare sulla base di. quindi fai solo IFS = "." Spero che ti aiuti :)


3
Ho dovuto spostare IFS = "" prima dell'assegnazione dell'array ma questa è la risposta corretta.
rapina il

Sto usando diverse matrici per analizzare le informazioni e avrò l'effetto di IFS = "" lavorando in una sola di esse. Una volta che utilizzo IFS = "" tutti gli altri array smettono di analizzare di conseguenza. Qualche suggerimento al riguardo?
Paulo Pedroso,

Paulo, vedi un'altra risposta qui che potrebbe essere migliore per il tuo caso: stackoverflow.com/a/9089186/1041319 . Non ho provato IFS = "" e sembra che lo risolva elegantemente, ma il tuo esempio mostra perché in alcuni casi si potrebbero riscontrare problemi. Potrebbe essere possibile impostare IFS = "" su una sola riga, ma potrebbe essere ancora più confuso rispetto all'altra soluzione.
Arntg,

Ha funzionato anche per me su bash. Grazie @Khushneet L'ho cercato per mezz'ora ...
csonuryilmaz,

Grande, unica risposta su questa pagina che ha funzionato. Ma ho anche dovuto spostare la IFS="" costruzione della matrice prima .
pkamb,

13

Concordo con gli altri sul fatto che è probabile che tu stia accedendo agli elementi che costituisce il problema. La citazione dei nomi dei file nell'assegnazione dell'array è corretta:

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

L'uso di virgolette doppie attorno a qualsiasi array del modulo "${FILES[@]}"divide l'array in una parola per elemento dell'array. Non fa alcuna scissione delle parole oltre a ciò.

Anche l'utilizzo "${FILES[*]}"ha un significato speciale, ma unisce gli elementi dell'array con il primo carattere di $ IFS, risultando in una parola, che probabilmente non è quello che si desidera.

Usando uno ${array[@]}o più ${array[*]}soggetti il ​​risultato di quell'espansione per un'ulteriore suddivisione delle parole, così finirai con le parole divise negli spazi (e qualsiasi altra cosa in $IFS) invece di una parola per elemento dell'array.

L'uso di uno stile C per il ciclo va anche bene ed evita di preoccuparti della divisione delle parole se non sei chiaro su di esso:

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

3

Opere di fuga.

#!/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]}

Produzione:

$ ./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

Anche la citazione delle stringhe produce lo stesso output.


3

Se avevi il tuo array in questo modo: #! / Bin / bash

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

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

Otterresti:

Debian
Red
Hat
Ubuntu
Suse

Non so perché, ma il ciclo scompone gli spazi e li mette come un singolo elemento, anche se lo circondi tra virgolette.

Per ovviare a questo, invece di chiamare gli elementi nell'array, si chiamano gli indici, che accetta l'intera stringa racchiusa tra virgolette. Deve essere racchiuso tra virgolette!

#!/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

Quindi otterrai:

Debian
Red Hat
Ubuntu
Suse

2

Non esattamente una risposta al problema di quoting / escape della domanda originale, ma probabilmente qualcosa che sarebbe stato effettivamente più utile per l'operazione:

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

Dove ovviamente l'espressione dovrebbe essere adottata per il requisito specifico (ad esempio *.jpgper tutti o 2001-09-11*.jpgsolo per le immagini di un determinato giorno).


0

Un'altra soluzione sta usando un ciclo "while" invece di un ciclo "for":

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

0

Se non sei bloccato sull'uso bash, una diversa gestione degli spazi nei nomi dei file è uno dei vantaggi della conchiglia . Si consideri una directory che contiene due file: "a b.txt" e "b c.txt". Ecco una supposizione ragionevole all'elaborazione di un elenco di file generati da un altro comando con bash, ma non riesce a causa di spazi nei nomi dei file che hai riscontrato:

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

Con fish, la sintassi è quasi identica, ma il risultato è quello che ti aspetteresti:

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

Funziona diversamente perché fish divide l'output dei comandi su newline, non su spazi.

Se hai un caso in cui vuoi dividere gli spazi anziché le nuove righe, fishha una sintassi molto leggibile per questo:

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

0

Una volta fatto, ripristinavo il valore IFS e il rollback.

# 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}

Possibile uscita dal loop:

2011-09-04 21.43.02.jpg

05/09/2011 10.23.14.jpg

2011-09-09 12.31.16.jpg

2011-09-11 08.43.12.jpg

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.