bash: la variabile perde valore alla fine del ciclo di lettura


36

Ho un problema in uno dei miei script di shell. Ho chiesto ad alcuni colleghi, ma tutti scuotono la testa (dopo qualche graffio), quindi sono venuto qui per una risposta.

Secondo la mia comprensione, il seguente script di shell dovrebbe stampare "Count is 5" come ultima riga. Solo che non lo fa. Stampa "Count is 0". Se il "while read" viene sostituito con qualsiasi altro tipo di loop, funziona perfettamente. Ecco la sceneggiatura:

echo "1"> input.data
echo "2" >> input.data
echo "3" >> input.data
echo "4" >> input.data
echo "5" >> input.data

CNT = 0 

cat input.data | durante la lettura;
fare
  lascia CNT ++;
  echo "Conteggio fino a $ CNT"
fatto 
echo "Il conteggio è $ CNT"

Perché succede e come posso prevenirlo? Ho provato questo in Debian Lenny e Squeeze, stesso risultato (es. Bash 3.2.39 e bash 4.1.5. Ammetto pienamente di non essere un mago di script di shell, quindi qualsiasi suggerimento sarebbe apprezzato.

Risposte:


30

Vedi argomento @ FAQ FAQ # 24: "Ho impostato le variabili in un ciclo. Perché improvvisamente scompaiono dopo che il ciclo è terminato? Oppure, perché non riesco a leggere i dati?" (più recentemente archiviato qui ).

Riepilogo: è supportato solo da bash 4.2 in poi. È necessario utilizzare diversi modi come le sostituzioni di comandi anziché una pipe se si utilizza bash.


Ottieni il bonus, poiché la tua risposta mi ha fornito la più ampia gamma di opzioni.
Wolfgangsz,

5
Il link è morto. Questo è il motivo per cui le risposte solo link sono cattive. Almeno riassumi qui la risposta.
Rudolfbyker,

Dio, l'ennesima volta in cui ksh è semplicemente molto meglio ... perché, proprio perché tutti si sono precipitati attorno a bash.
Florian Heigl,

@FlorianHeigl: Stai affermando che ksh è One True Shell?
Ignacio Vazquez-Abrams,

@ IgnacioVazquez-Abrams no, ma sostengo che la gestione del ciclo while in bash sia un PITA orribile. La gestione del loop è stata il lungo termine che gli ha impedito di catturare la funzionalità del 1993. Le altre cose sono la gestione getopt in cui il gestore incorporato (anche 1993) era semplice e capace, qualcosa che non si può ancora ottenere se non si utilizza ie docopt. Sto sostenendo che bash si è messo dietro la curva per oltre 20 anni, con insistenza e la quantità di tempo speso per QUESTA COSA QUI o milioni di usi di cattive truppe è fuori misura - accettata solo perché la maggior parte delle persone non lo saprà mai.
Florian Heigl,

30

Questo è un tipo di errore "comune". Le pipe creano Sottothell, quindi while readè in esecuzione su una shell diversa rispetto allo script, in modo che la CNTvariabile non cambi mai (solo quella all'interno della subshell di pipa).

Raggruppa l'ultimo echocon la subshell whileper risolverlo (ci sono molti altri modi per risolverlo, questo è uno. Le risposte di Iain e Ignacio ne hanno altre).

CNT=0

 cat input.data | ( while read 
do
  let CNT++;
  echo "Counting to $CNT"
done 
echo "Count is $CNT" )

Spiegazione lunga:

  1. Dichiari CNTsul tuo script di essere il valore 0;
  2. Una Sottoshell viene avviata |al punto while read;
  3. La $CNTvariabile viene esportata nella sottoshell con valore 0;
  4. La sottoshell conta e aumenta il CNTvalore a 5;
  5. Fine della sotto-shell, variabili e valori vengono distrutti (non tornano al processo / script di chiamata).
  6. Il echotuo CNTvalore originale di 0.

2
La prima sceneggiatura di shell che abbia mai scritto mi ha dato gli stessi problemi, ho sbattuto la testa contro il muro per un po 'prima di scoprire che quelle pipe generano ulteriori shell. Qualsiasi variabile con cui si scherza in una pipe uscirà dal campo di applicazione non appena termina la pipe, il che significa che se si vuole davvero fare qualcosa con una variabile al di fuori della pipe in cui è stata utilizzata, è necessario mantieni lo stato attraverso qualcosa di funky come un file temporaneo.
fotoionizzata il

Ottima risposta, purtroppo posso dare solo un bonus di accettazione. Scusate.
Wolfgangsz,

10

Questo funziona

CNT=0 

while read ;
do
  let CNT++;
  echo "Counting to $CNT"
done <input.data
echo "Count is $CNT"

Mi piace, il modo intelligente perché sai dove sono i dati necessari e devi solo recuperarli. Se non conosci soluzioni ad alta competenza, puoi sempre "leggere un file" ahahhha. +1 per te.
m3nda,

1
Chiunque legga questo, sappi che la soluzione fornita da Iain funziona solo quando il tuo script richiama esplicitamente bash, avendo la prima riga: #! / Bin / bash e che: #! / Bin / sh non funzionerà.
Roadowl

1
Interessante, primo esempio che abbia mai visto dove un uso inutile di Cat ha effettivamente impedito il funzionamento del codice . A proposito di @Roadowl, l'unico bashism qui è la linea let CNT++che dovrebbe invece essere quella CNT="$((CNT+1))"di utilizzare l' espansione aritmetica conforme a POSIX . Il resto è già portatile.
Carattere jolly

6

Prova invece a passare i dati in una sotto-shell, come se fosse un file prima del ciclo while. Questo è simile alla soluzione di Lain, ma presume che tu non voglia un file intermittente:

total=0
while read var
do
  echo "variable: $var"
  ((total+=var))
done < <(echo 45) #output from a command, script, or function
echo "total: $total"
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.