2012-08-13 10 views
6

Posso farlo funzionare in ksh ma non in bash che mi sta facendo impazzire. Speriamo che sia ovvio che sto trascurando.problemi di bash che assegnano a un indice di array in un ciclo

Ho bisogno di eseguire un comando esterno in cui ogni riga dell'output verrà memorizzata in un indice di array.

Questo esempio semplificato ha l'aspetto di impostare correttamente l'array nel ciclo, tuttavia, una volta completato il ciclo, le assegnazioni di array sono sparite? È come se il ciclo fosse trattato completamente come un guscio esterno?

rusco.txt

this is a 
test to see 
if this works ok 

testa.sh

#!/bin/bash 

declare -i i=0 
declare -a array 

echo "Simple Test:" 
array[0]="hello" 
echo "array[0] = ${array[0]}" 

echo -e "\nLoop through junk.txt:" 
cat junk.txt | while read line 
do 
    array[i]="$line" 
    echo "array[$i] = ${array[i]}" 
    let i++ 
done 

echo -e "\nResults:" 
echo "  array[0] = ${array[0]}" 
echo " Total in array = ${#array[*]}" 
echo "The whole array:" 
echo ${array[@]} 

uscita

Simple Test: 
array[0] = hello 

Loop through junk.txt: 
array[0] = this is a 
array[1] = test to see 
array[2] = if this works ok 

Results: 
     array[0] = hello 
Total in array = 1 
The whole array: 
hello 

Così, mentre nel loop, assegniamo array [i] e l'eco verifica. Ma dopo il ciclo sono tornato all'array [0] contenente "ciao" senza altri elementi.

Stessi risultati su bash 3, 4 e piattaforme diverse.

risposta

7

Poiché il ciclo while si trova in una pipeline, tutte le assegnazioni di variabili nel corpo del ciclo sono locali rispetto alla subshell in cui viene eseguito il ciclo. (Credo ksh non eseguire il comando in una subshell, che è il motivo per cui si ha il problema in bash.) Fate questo invece:

while read line 
do 
    array[i]="$line" 
    echo "array[$i] = ${array[i]}" 
    let i++ 
done < junk.txt 

Raramente, se non mai, vuoi utilizzare cat a tubo un singolo file in un altro comando; utilizzare invece il reindirizzamento dell'input.

UPDATE: da quando è necessario eseguire da un comando e non un file, un'altra opzione (se disponibile) è la sostituzione di processo:

while read line; do 
... 
done < <(command args ...) 

Se sostituzione di processo non è disponibile, è necessario output un file temporaneo e reindirizzare l'input da quel file.

Se si utilizza bash 4.2 o versione successiva, è possibile eseguire questi due comandi prima del vostro ciclo, e il tubo-in-the-loop originale funziona, dal momento che il ciclo while è l'ultimo comandi in cantiere.

set +m # Turn off job control; it's probably already off in a non-interactive script 
shopt -s lastpipe 
cat junk.txt | while read line; do ...; done 

UPDATE 2: Ecco una soluzione loop-meno in base a commento di user1596414

array[0]=hello 
IFS=$'\n' array+=($(command)) 

L'output del comando è diviso in parole basata esclusivamente su ritorni a capo (in modo che ogni riga è una parola separata) e aggiunge la matrice line-per-slot risultante all'originale. Questo è molto utile se si utilizza solo il loop per costruire l'array. Può anche essere probabilmente modificato per far fronte a una piccola quantità di elaborazione per linea, vagamente simile alla comprensione di un elenco Python.

+0

"Devo eseguire un comando esterno in cui ogni riga dell'output verrà archiviata in un indice di array." Il "gatto" era un esempio semplificato. Ho bisogno di eseguire un comando e ottenere l'output nel ciclo e un reindirizzamento come user1596414

+0

Aggiornato con alcune opzioni. L'ultimo (se la tua versione di bash è abbastanza nuova) è probabilmente quella che vuoi. – chepner

+1

+1 un file temporaneo o le opzioni di bash 4.2 (o successive) funzionano. Esiste una terza opzione che utilizza IFS per gestire gli spazi bianchi e può essere combinata con un semplice array assegnato se non è necessario ripetere l'iterazione attraverso l'output. – user1596414

Problemi correlati