Jak przesłać dane wejściowe do pętli Bash while i zachować zmienne po zakończeniu pętli

87

Bash pozwala na użycie: cat <(echo "$FILECONTENT")

Bash pozwala również na użycie: while read i; do echo $i; done </etc/passwd

aby połączyć poprzednie dwa, można użyć: echo $FILECONTENT | while read i; do echo $i; done

Problem z ostatnią polega na tym, że tworzy ona podpowłokę i po zakończeniu pętli while inie ma już dostępu do zmiennej .

Moje pytanie brzmi:

Jak osiągnąć coś takiego: while read i; do echo $i; done <(echo "$FILECONTENT")czyli innymi słowy: skąd mam pewność, że iprzetrwa pętlę while?

Zwróć uwagę, że jestem świadomy dołączenia instrukcji while do, {}ale to nie rozwiązuje problemu (wyobraź sobie, że chcesz użyć pętli while w funkcji i izmiennej zwracanej )

Wakan Tanka
źródło
Powiązane: stackoverflow.com/questions/37229058/… . Wyjaśnia wszystkie opcje, w tym wymienione poniżej zastąpienie procesu lastpipeoraz ich wady i zalety.
ivan_pozdeev

Odpowiedzi:

115

Prawidłowy zapis zastępowania procesu to:

while read i; do echo $i; done < <(echo "$FILECONTENT")

Ostatnia wartość iprzypisana w pętli jest wtedy dostępna po zakończeniu pętli. Alternatywą jest:

echo $FILECONTENT | 
{
while read i; do echo $i; done
...do other things using $i here...
}

Nawiasy klamrowe są operacją grupowania we / wy i same nie tworzą podpowłoki. W tym kontekście są one częścią potoku i dlatego są uruchamiane jako podpowłoka, ale dzieje się tak z powodu |, a nie { ... }. Wspomniałeś o tym w pytaniu. AFAIK, możesz wykonać zwrot z tych elementów wewnątrz funkcji.


Bash zapewnia również shoptwbudowaną, a jedną z wielu opcji jest:

lastpipe

Jeśli jest ustawiona, a kontrola zadań nie jest aktywna, powłoka uruchamia ostatnie polecenie potoku niewykonanego w tle w bieżącym środowisku powłoki.

Dlatego użycie czegoś takiego w skrypcie sprawia, że ​​zmodyfikowany plik jest sumdostępny po pętli:

FILECONTENT="12 Name
13 Number
14 Information"
shopt -s lastpipe   # Comment this out to see the alternative behaviour
sum=0
echo "$FILECONTENT" |
while read number name; do ((sum+=$number)); done
echo $sum

Robienie tego w wierszu poleceń zwykle powoduje błąd „kontrola zadań nie jest aktywna” (to znaczy w wierszu poleceń kontrola zadań jest aktywna). Testowanie tego bez użycia skryptu nie powiodło się.

Ponadto, jak zauważył Gareth Rees w swojej odpowiedzi , czasami możesz użyć tutaj ciągu :

while read i; do echo $i; done <<< "$FILECONTENT"

To nie wymaga shopt; możesz być w stanie zapisać proces za jego pomocą.

Jonathan Leffler
źródło
Przepraszam za moją ignorancję. Wiem, że to dobre rozwiązanie i oznaczyłem je jako odpowiedź, więc zadziałało. Ale teraz, kiedy go uruchomię, while read i; do echo $i; done < <(cat /etc/passwd); echo $inie wróciłem do ostatniej linii dwa razy. Co robię źle?
Wakan Tanka
@WakanTanka: Musiałem trochę poeksperymentować ... Wydaje mi się, że odpowiedź jest taka, że ​​błąd odczytu resetuje się ido pustego, więc echo po pętli powtarza pusty wiersz.
Jonathan Leffler
1
Uznanie za odniesienie do podstawienia procesu. Nie byłem tego świadomy.
Atcold
@Wakan Tanka: uzyskałem taki sam wynik jak twój, używam while read i; do x=$i; done < <(cat /etc/passwd); echo i=$i; echo x=$xdziałał, // może wtedy zmieniło się zachowanie basha?
yurenchen
Ratujesz życie! Od wielu godzin szukałem podobnego rozwiązania. Twój jest jedynym, który działał.
Pat
0

Ta funkcja tworzy duplikaty $ NUM razy plików jpg (bash)

function makeDups() {
NUM=$1
echo "Making $1 duplicates for $(ls -1 *.jpg|wc -l) files"
ls -1 *.jpg|sort|while read f
do
  COUNT=0
  while [ "$COUNT" -le "$NUM" ]
  do
    cp $f ${f//sm/${COUNT}sm}
    ((COUNT++))
  done
done
}
Steve
źródło