Attraversa una serie di stringhe in Bash?


1503

Voglio scrivere una sceneggiatura che si snoda su 15 stringhe (matrice forse?) È possibile?

Qualcosa di simile a:

for databaseName in listOfNames
then
  # Do something
end

Risposte:


2392

Puoi usarlo in questo modo:

## declare an array variable
declare -a arr=("element1" "element2" "element3")

## now loop through the above array
for i in "${arr[@]}"
do
   echo "$i"
   # or do whatever with individual element of the array
done

# You can access them using echo "${arr[0]}", "${arr[1]}" also

Funziona anche per la dichiarazione di array multilinea

declare -a arr=("element1" 
                "element2" "element3"
                "element4"
                )

777

Questo è possibile, ovviamente.

for databaseName in a b c d e f; do
  # do something like: echo $databaseName
done 

Vedi Bash Loops per, mentre e fino ai dettagli.


18
Qual è il problema con questo approccio? In casi semplici sembra funzionare e, quindi, è più intuitivo della risposta di @ anubhava.
Dr. Jan-Philip Gehrcke,

17
Questo funziona particolarmente bene con la sostituzione dei comandi, ad es for year in $(seq 2000 2013).
Brad Koch,

23
Il problema è che ha chiesto informazioni sull'iterazione attraverso un array.
mgalgs

20
L'approccio 'declare' funziona meglio se devi iterare sullo stesso array in più di un posto. Questo approccio è più pulito ma meno flessibile.
StampyCode

15
Perché questo non è il numero 1? È più pulito e puoi riutilizzare facilmente l'array semplicemente impostando una stringa, ovvero DATABASES="a b c d e f".
Nerdmaster,

201

Nessuna di queste risposte include un contatore ...

#!/bin/bash
## declare an array variable
declare -a array=("one" "two" "three")

# get length of an array
arraylength=${#array[@]}

# use for loop to read all values and indexes
for (( i=1; i<${arraylength}+1; i++ ));
do
  echo $i " / " ${arraylength} " : " ${array[$i-1]}
done

Produzione:

1  /  3  :  one
2  /  3  :  two
3  /  3  :  three

7
Questa dovrebbe essere la risposta accettata, è l'unica che funziona quando gli elementi dell'array contengono spazi.
jesjimher,

1
Questo è solo per il bene dell'esempio con un contatore. È anche abbastanza banale cambiarlo e funziona allo stesso modo.
caktux,

7
L'eco alla fine è buggy. Non hai bisogno di citare costanti, devi citare espansioni, o puoi tranquillamente citare entrambi come segue: echo "$i / ${arraylength} : ${array[$i-1]}"- altrimenti, se il tuo $icontiene un glob verrà espanso, se contiene un tab verrà cambiato in uno spazio, ecc.
Charles Duffy,

10
@bzeaman, certo - ma se sei sciatto su queste cose, allora richiede un'analisi contestuale (come hai appena fatto) per dimostrare qualcosa di corretto e una nuova analisi se quel contesto cambia o il codice viene riutilizzato in un posto diverso o per qualunque altra ragione sia possibile un flusso di controllo imprevisto. Scrivilo in modo robusto ed è corretto indipendentemente dal contesto.
Charles Duffy,

5
Questo non funzionerà per una matrice sparsa, ovvero se alla matrice mancano elementi. Ad esempio, se si hanno elementi dell'array A [1] = 'xx', A [4] = 'yy' e A [9] = 'zz', la lunghezza sarà 3 e il ciclo non elaborerà tutti gli elementi .
Mr Ed

145

for Item in Item1 Item2 Item3 Item4 ;
  do
    echo $Item
  done

Produzione:

Item1
Item2
Item3
Item4

Preservare gli spazi; voci dell'elenco di virgolette singole o doppie e espansioni dell'elenco di virgolette doppie.

for Item in 'Item 1' 'Item 2' 'Item 3' 'Item 4' ;
  do
    echo "$Item"
  done

Produzione:

Item 1
Item 2
Item 3
Item 4

Per fare la lista su più righe

for Item in Item1 \
            Item2 \
            Item3 \
            Item4
  do
    echo $Item
  done

Produzione:

Item1
Item2
Item3
Item4


Variabile di elenco semplice

List=( Item1 Item2 Item3 )

o

List=(
      Item1 
      Item2 
      Item3
     )

Visualizza la variabile elenco:

echo ${List[*]}

Produzione:

Item1 Item2 Item3

Scorri l'elenco:

for Item in ${List[*]} 
  do
    echo $Item 
  done

Produzione:

Item1
Item2
Item3

Crea una funzione per scorrere un elenco:

Loop(){
  for item in ${*} ; 
    do 
      echo ${item} 
    done
}
Loop ${List[*]}

Utilizzo della parola chiave declare (comando) per creare l'elenco, che tecnicamente viene chiamato un array:

declare -a List=(
                 "element 1" 
                 "element 2" 
                 "element 3"
                )
for entry in "${List[@]}"
   do
     echo "$entry"
   done

Produzione:

element 1
element 2
element 3

Creazione di un array associativo. Un dizionario:

declare -A continent

continent[Vietnam]=Asia
continent[France]=Europe
continent[Argentina]=America

for item in "${!continent[@]}"; 
  do
    printf "$item is in ${continent[$item]} \n"
  done

Produzione:

 Argentina is in America
 Vietnam is in Asia
 France is in Europe

Variabili CVS o file in un elenco .
Cambiando il separatore di campo interno da uno spazio, a quello che vuoi sempre.
Nell'esempio seguente viene cambiato in una virgola

List="Item 1,Item 2,Item 3"
Backup_of_internal_field_separator=$IFS
IFS=,
for item in $List; 
  do
    echo $item
  done
IFS=$Backup_of_internal_field_separator

Produzione:

Item 1
Item 2
Item 3

Se è necessario numerarli:

` 

questo si chiama back tick. Metti il ​​comando all'interno delle zecche posteriori.

`commend` 

Si trova accanto al numero uno sulla tastiera e sopra il tasto tab. Su una tastiera standard in inglese americano.

List=()
Start_count=0
Step_count=0.1
Stop_count=1
for Item in `seq $Start_count $Step_count $Stop_count`
    do 
       List+=(Item_$Item)
    done
for Item in ${List[*]}
    do 
        echo $Item
    done

L'output è:

Item_0.0
Item_0.1
Item_0.2
Item_0.3
Item_0.4
Item_0.5
Item_0.6
Item_0.7
Item_0.8
Item_0.9
Item_1.0

Acquisire maggiore familiarità con il comportamento bashes:

Crea un elenco in un file

cat <<EOF> List_entries.txt
Item1
Item 2 
'Item 3'
"Item 4"
Item 7 : *
"Item 6 : * "
"Item 6 : *"
Item 8 : $PWD
'Item 8 : $PWD'
"Item 9 : $PWD"
EOF

Leggere il file elenco in un elenco e visualizzarlo

List=$(cat List_entries.txt)
echo $List
echo '$List'
echo "$List"
echo ${List[*]}
echo '${List[*]}'
echo "${List[*]}"
echo ${List[@]}
echo '${List[@]}'
echo "${List[@]}"

Manuale di riferimento della riga di comando BASH: significato speciale di determinati caratteri o parole nella shell.


7
QUESTO È SBAGLIATO. Deve "${List[@]}"essere corretto, con le virgolette . ${List[@]}è sbagliato. ${List[*]}è sbagliato. Prova List=( "* first item *" "* second item *" ): otterrai un comportamento corretto per for item in "${List[@]}"; do echo "$item"; done, ma non da qualsiasi altra variante.
Charles Duffy,

Sì, vengono interpretati caratteri speciali, che possono essere desiderati o meno. Ho aggiornato la risposta per includerla.
FireInTheSky

2
Suggerirei vivamente di mostrare prima l' approccio più corretto / solido . La gente spesso prende la prima risposta che sembra funzionare; se quella risposta ha avvertimenti nascosti, possono esporsi solo in seguito. (Non è solo caratteri jolly che vengono metabolizzati dalla mancanza di citazione; List=( "first item" "second item" )sarà suddiviso in first, item, second, itempure).
Charles Duffy,

Potresti anche considerare di evitare l'uso di un esempio che potrebbe indurre le persone ad analizzare i lsrisultati, in violazione delle migliori pratiche .
Charles Duffy,

1
Stai confutando affermazioni che non ho mai fatto. Conosco abbastanza bene la semantica delle citazioni di Bash - vedi stackoverflow.com/tags/bash/topusers
Charles Duffy,

108

Nello stesso spirito della risposta di 4ndrew:

listOfNames="RA
RB
R C
RD"

# To allow for other whitespace in the string:
# 1. add double quotes around the list variable, or
# 2. see the IFS note (under 'Side Notes')

for databaseName in "$listOfNames"   #  <-- Note: Added "" quotes.
do
  echo "$databaseName"  # (i.e. do action / processing of $databaseName here...)
done

# Outputs
# RA
# RB
# R C
# RD

B. Nessuno spazio nei nomi:

listOfNames="RA
RB
R C
RD"

for databaseName in $listOfNames  # Note: No quotes
do
  echo "$databaseName"  # (i.e. do action / processing of $databaseName here...)
done

# Outputs
# RA
# RB
# R
# C
# RD

Appunti

  1. Nel secondo esempio, l'utilizzo listOfNames="RA RB R C RD"ha lo stesso output.

Altri modi per inserire i dati includono:

Leggi da stdin

# line delimited (each databaseName is stored on a line)
while read databaseName
do
  echo "$databaseName"  # i.e. do action / processing of $databaseName here...
done # <<< or_another_input_method_here
  1. il delimitatore bash IFS "separatore di campo alla linea" [ 1 ] può essere specificato nello script per consentire altri spazi bianchi (ovvero IFS='\n', o per MacOS IFS='\r')
  2. Mi piace anche la risposta accettata :): ho incluso questi frammenti come altri modi utili per rispondere alla domanda.
  3. L'inclusione #!/bin/bashnella parte superiore del file di script indica l'ambiente di esecuzione.
  4. Mi ci sono voluti mesi per capire come codificare questo semplicemente :)

Altre fonti ( durante il ciclo di lettura )


Questo crea l'impressione che eol sia usato come separatore di stringhe e, pertanto, gli spazi bianchi sono consentiti all'interno delle stringhe. Tuttavia, le stringhe con spazi bianchi sono ulteriormente separate in sottostringhe, il che è molto male. Penso che questa risposta stackoverflow.com/a/23561892/1083704 sia migliore.
Val

1
@Val, ho aggiunto un commento in codice con un riferimento a IFS. (Per tutti, IFSconsente di specificare un delimitatore specifico, che consente ad altri spazi bianchi di essere inclusi nelle stringhe senza essere separati in sottostringhe).
user2533809

7
Questo non funziona per me. $databaseNamecontiene solo l'intero elenco, quindi esegue solo una singola iterazione.
AlikElzin-Kilaka,

@ AlikElzin-kilaka La mia risposta di seguito risolve questo problema in modo che il ciclo venga eseguito per ogni riga della stringa.
Jamie,

42

È possibile utilizzare la sintassi di ${arrayName[@]}

#!/bin/bash
# declare an array called files, that contains 3 values
files=( "/etc/passwd" "/etc/group" "/etc/hosts" )
for i in "${files[@]}"
do
    echo "$i"
done

31

Sorpreso dal fatto che nessuno l'abbia ancora pubblicato - se hai bisogno degli indici degli elementi mentre esegui il loop nell'array, puoi farlo:

arr=(foo bar baz)

for i in ${!arr[@]}
do
    echo $i "${arr[i]}"
done

Produzione:

0 foo
1 bar
2 baz

Lo trovo molto più elegante dello stile "tradizionale" for-loop ( for (( i=0; i<${#arr[@]}; i++ ))).

( ${!arr[@]}e $inon è necessario che siano citati perché sono solo numeri; alcuni suggeriscono comunque di citarli, ma si tratta solo di preferenze personali.)


2
Questa dovrebbe davvero essere la risposta scelta. 1: è semplice e facile da leggere. 2: gestisce correttamente gli spazi bianchi, nessuna assurdità IFS si mette in mezzo. 3: gestisce correttamente le matrici sparse.
sig

20

Anche questo è facile da leggere:

FilePath=(
    "/tmp/path1/"    #FilePath[0]
    "/tmp/path2/"    #FilePath[1]
)

#Loop
for Path in "${FilePath[@]}"
do
    echo "$Path"
done

4
Questo è chiaro e ha funzionato per me (anche con gli spazi e la sostituzione delle variabili negli elementi dell'array FilePath) solo quando ho impostato correttamente la variabile IFS prima della definizione dell'array FilePath: IFS=$'\n' questo potrebbe funzionare anche per altre soluzioni in questo scenario.
Alan Forsyth,

8

Matrice implicita per script o funzioni:

Oltre alla risposta corretta di anubhava : Se la sintassi di base per loop è:

for var in "${arr[@]}" ;do ...$var... ;done

c'è un caso speciale in:

Quando si esegue uno script o una funzione, gli argomenti passati a righe di comando saranno assegnati a $@variabile di matrice, è possibile accedere $1, $2, $3, e così via.

Questo può essere popolato (per il test) da

set -- arg1 arg2 arg3 ...

Un loop su questo array potrebbe essere scritto semplicemente:

for item ;do
    echo "This is item: $item."
  done

Nota che il lavoro riservato innon è presente e nessun nome di array!

Campione:

set -- arg1 arg2 arg3 ...
for item ;do
    echo "This is item: $item."
  done
This is item: arg1.
This is item: arg2.
This is item: arg3.
This is item: ....

Si noti che questo è lo stesso di

for item in "$@";do
    echo "This is item: $item."
  done

Quindi in una sceneggiatura :

#!/bin/bash

for item ;do
    printf "Doing something with '%s'.\n" "$item"
  done

Salva questo in uno script myscript.sh, chmod +x myscript.she poi

./myscript.sh arg1 arg2 arg3 ...
Doing something with 'arg1'.
Doing something with 'arg2'.
Doing something with 'arg3'.
Doing something with '...'.

Lo stesso in una funzione :

myfunc() { for item;do cat <<<"Working about '$item'."; done ; }

Poi

myfunc item1 tiem2 time3
Working about 'item1'.
Working about 'tiem2'.
Working about 'time3'.

7
listOfNames="db_one db_two db_three"
for databaseName in $listOfNames
do
  echo $databaseName
done

o solo

for databaseName in db_one db_two db_three
do
  echo $databaseName
done

7

Modo semplice:

arr=("sharlock"  "bomkesh"  "feluda" )  ##declare array

len=${#arr[*]}  # it returns the array length

#iterate with while loop
i=0
while [ $i -lt $len ]
do
    echo ${arr[$i]}
    i=$((i+1))
done


#iterate with for loop
for i in $arr
do
  echo $i
done

#iterate with splice
 echo ${arr[@]:0:3}

6

L'array declare non funziona per la shell Korn. Usa l'esempio seguente per la shell Korn:

promote_sla_chk_lst="cdi xlob"

set -A promote_arry $promote_sla_chk_lst

for i in ${promote_arry[*]};
    do
            echo $i
    done

2
prova l'evidenziatore di codice nell'editor per far apparire bene il tuo codice.
Dove

5
Buono a sapersi, ma questa domanda riguarda bash.
Brad Koch,

1
Molti bug qui. Non può avere voci di elenco con spazi, non può avere voci di elenco con caratteri glob. for i in ${foo[*]}è fondamentalmente sempre la cosa sbagliata - for i in "${foo[@]}"è la forma che preserva i confini dell'elenco originale e impedisce l'espansione globale. E l'eco deve essereecho "$i"
Charles Duffy,

4

Questo è simile alla risposta dell'utente 2533809, ma ogni file verrà eseguito come comando separato.

#!/bin/bash
names="RA
RB
R C
RD"

while read -r line; do
    echo line: "$line"
done <<< "$names"

3

Se stai usando la shell Korn, c'è " set -A databaseName ", altrimenti c'è " declare -a databaseName "

Per scrivere uno script funzionante su tutte le shell,

 set -A databaseName=("db1" "db2" ....) ||
        declare -a databaseName=("db1" "db2" ....)
# now loop 
for dbname in "${arr[@]}"
do
   echo "$dbname"  # or whatever

done

Dovrebbe funzionare su tutte le shell.


3
No, no: $ bash --versionGNU bash, versione 4.3.33 (0) -release (amd64-portbld-freebsd10.0) $ set -A databaseName=("db1" "db2" ....) || declare -a databaseName=("db1" "db2" ....)bash: errore di sintassi vicino token inaspettato `('
Bernie Reiter

2

Prova questo. Funziona e testato.

for k in "${array[@]}"
do
    echo $k
done

# For accessing with the echo command: echo ${array[0]}, ${array[1]}

3
Questo in realtà non funziona correttamente. Prova array=( "hello world" ), oppure arrray=( "*" ); nel primo caso verrà stampato helloe worldseparatamente, nel secondo verrà stampato un elenco di file anziché il*
Charles Duffy,

6
... prima di chiamare qualcosa "testato" nella shell, assicurati di controllare i casi angolari, che sono spazi bianchi e globs.
Charles Duffy,

2

Possibile prima riga di ogni script / sessione di Bash:

say() { for line in "${@}" ; do printf "%s\n" "${line}" ; done ; }

Usa ad esempio:

$ aa=( 7 -4 -e ) ; say "${aa[@]}"
7
-4
-e

Può considerare: echointerpreta -ecome opzione qui


2

Loop a linea singola,

 declare -a listOfNames=('db_a' 'db_b' 'db_c')
 for databaseName in ${listOfNames[@]}; do echo $databaseName; done;

otterrai un output come questo,

db_a
db_b
db_c

2

Ciò di cui avevo davvero bisogno per questo era qualcosa del genere:

for i in $(the_array); do something; done

Per esempio:

for i in $(ps -aux | grep vlc  | awk '{ print $2 }'); do kill -9 $i; done

(Ucciderebbe tutti i processi con vlc nel loro nome)


1

Ciclo attraverso una serie di miei progetti per un git pullaggiornamento:

#!/bin/sh
projects="
web
ios
android
"
for project in $projects do
    cd  $HOME/develop/$project && git pull
end

7
Sebbene questo frammento di codice possa risolvere la domanda, inclusa una spiegazione aiuta davvero a migliorare la qualità del tuo post. Ricorda che in futuro stai rispondendo alla domanda dei lettori e che queste persone potrebbero non conoscere i motivi del tuo suggerimento sul codice. Cerca anche di non aggiungere il tuo codice a commenti esplicativi, ciò riduce la leggibilità sia del codice che delle spiegazioni!
Kayess,
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.