2011-01-12 9 views
34

Sto cercando di trovare il percorso con il maggior numero di caratteri in esso. Ci potrebbero essere modi migliori per farlo. Ma mi piacerebbe sapere perché questo problema si verifica.Variabili di shell impostate all'interno del ciclo while non visibili al di fuori di esso

LONGEST_CNT=0 
find samples/ | while read line 
do 
    line_length=$(echo $line | wc -m) 
    if [[ $line_length -gt $LONGEST_CNT ]] 
    then 
     LONGEST_CNT=$line_length 
     LONGEST_STR=$line 
    fi 
done 

echo $LONGEST_CNT : $LONGEST_STR 

Si ritorna in qualche modo sempre:

0 : 

Se stampare i risultati per il debug all'interno del ciclo, mentre i valori sono corretti. Quindi, perché bash non rende queste variabili globali?

risposta

70

Quando si configura un loop while in Bash, viene creata una sottochiave. Quando esce la subshell, tutte le variabili ritornano ai loro valori precedenti (che possono essere nulli o non impostati). Questo può essere prevenuto usando la sostituzione di processo.

LONGEST_CNT=0 
while read -r line 
do 
    line_length=${#line} 
    if ((line_length > LONGEST_CNT)) 
    then 
     LONGEST_CNT=$line_length 
     LONGEST_STR=$line 
    fi 
done < <(find samples/) # process substitution 

echo $LONGEST_CNT : $LONGEST_STR 
+0

Grazie, questo mi ha aiutato molto. – Mark

+0

La soluzione fornita da Dennis funziona, ma si tenga presente che viola la norma POSIX. Prova a 'set -o posix' e lo script non funzionerà! – RobSis

+2

@Robert: la domanda è contrassegnata [bash]. È vero che la sostituzione del processo non è specificata da POSIX. Tuttavia, a meno che la portabilità delle shell solo POSIX sia un problema, non c'è motivo di non utilizzare le funzionalità specifiche di Bash. A proposito, la sostituzione del processo è supportata da ksh e zsh. Tuttavia, non creano una subshell che causa la perdita della variabile. Si noti inoltre che Bash 4.2 ha un'opzione, 'shopt -s lastpipe' che" esegue l'ultimo comando di una pipeline nel contesto corrente della shell "(non una sottotitola) - a meno che il controllo del lavoro non abbia effetto. –

19

La risposta "corretta" è data da Dennis. Tuttavia, trovo il trucco di sostituzione del processo estremamente illeggibile se il loop contiene più di poche righe. Durante la lettura di uno script, voglio vedere cosa va nella pipe prima di vedere come viene elaborato.

Quindi di solito preferisco questo trucco di incapsulare il ciclo while in "{}".

LONGEST_CNT=0 
find /usr/share/zoneinfo | \ 
{ while read -r line 
    do 
     line_length=${#line} 
     if ((line_length > LONGEST_CNT)) 
     then 
      LONGEST_CNT=$line_length 
      LONGEST_STR=$line 
     fi 
    done 
    echo $LONGEST_CNT : $LONGEST_STR 
} 
+0

Completamente d'accordo sul punto di leggibilità. +1. Eppure, è la strada da percorrere. – 0xC0000022L

+2

con la differenza che questo esempio non modifica la variabile globale. se controlli LONGEST_CNT ** dopo ** il ciclo, vedrai che è ancora zero – Deim0s

+0

@ Deims0s: sì. Questo è il motivo per cui l'istruzione echo si trova all'interno di "{}". Se le variabili sono necessarie di nuovo in seguito, la modalità di sostituzione del processo potrebbe essere più adatta. – mivk

2

Informazioni sulla ricerca del percorso più lungo. Ecco un'alternativa:

find /usr/share/zoneinfo | while read line; do 
    echo ${#line} $line 
done | sort -nr | head -n 1 

# Result: 
58 /usr/share/zoneinfo/right/America/Argentina/ComodRivadavia 

Perdonami se questo è considerato fuori tema, spero che aiuti qualcuno.

+0

L'uso di 'echo' per stampare l'output dai backick è raramente utile o necessario. Vedi [uso inutile di 'echo'] (http://partmaps.org/era/unix/award.html#echo). – tripleee

+1

@ triplo: AFAICT entrambi gli echi sono utili in questo esempio: wc richiede l'input da $ line e $ line deve essere visualizzato dopo l'output da wc. Per favore, sentiti libero di suggerire miglioramenti senza aggiungere più righe o variabili. – grebneke

+2

@grebneke Ho messo in su la tua risposta (e commento) ma dopo averci pensato un po 'di più, ho capito che potresti evitare di usare la sostituzione di comando per eseguire 'wc'. Invece, potresti usare [Espansione parametro POSIX shell] (http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_05_02): 'echo $ {# line} $ line;' –

0

Fai quello che da sempre (dovrebbe) fare:

  • preoccupazioni separati,
  • evitare globali,
  • documento il codice,
  • essere leggibile,
  • forse essere POSIXy.

(Sì, ho aggiunto un po 'di più "buona prassi" ingredienti per la zuppa di quanto strettamente necessario;))

Quindi il mio preferito "reazione istintiva" per problemi con subshell invisibile è quello di utilizzare la funzione :

#!/bin/sh 

longest() { 
    # 
    # Print length and body of the longest line in STDIN 
    # 
    local cur_ln # current line 
    local cur_sz # current size (line length) 
    local max_sz # greatest size so far 
    local winner # longest string so far 
    max_sz=0 
    while read -r cur_ln 
    do 
     cur_sz=${#cur_ln} 
     if test "$cur_sz" -gt "$max_sz"; 
     then 
      max_sz=$cur_sz 
      winner=$cur_ln 
     fi 
    done 
    echo "$max_sz" : "$winner" 
} 

find /usr/share/zoneinfo | longest 

# ok, if you really wish to use globals, here you go ;) 
LONGEST_CNT=0 
LONGEST_CNT=$(
    find /usr/share/zoneinfo \ 
     | longest \ 
     | cut -d: -f1 \ 
     | xargs echo\ 
) 
echo "LONGEST_CNT='$LONGEST_CNT'" 

Oltre a evitare il fastidio subshell, ti dà luogo perfetto per documentare il codice, e una sorta-di aggiunge namespacing: notare che la funzione all'interno si possono usare nomi di variabili molto più brevi e più semplici senza perdere di leggibilità .

Problemi correlati