Powiedz, że mam ten plik:
hello world hello world
Ten program
#!/bin/bash for i in $(cat $1); do echo "tester: $i" done
wyjścia
tester: hello tester: world tester: hello tester: world
Chciałbym mieć for
iteruj po każdym wierszu z osobna, ignorując jednak białe znaki, tj. ostatnie dwie linie należy zastąpić przez
tester: hello world
Używając cudzysłowów for i in "$(cat $1)";
powoduje, że i
zostaje przypisany do całego pliku naraz. Co mam zmienić?
Odpowiedź
(9 lat później 🙂
Obie podane odpowiedzi zakończyłyby się niepowodzeniem na plikach bez znaku nowej linii na końcu, co skutecznie pominie ostatnią linię, nie spowoduje błędów, doprowadzi do katastrofy sposób :).
Najlepsze zwięzłe rozwiązanie, jakie do tej pory znalazłem, że „Just Works” (zarówno w bashu, jak i sh):
while IFS="" read -r LINE || [ -n "${LINE}" ]; do echo "processing line: ${LINE}" done < /path/to/input/file.txt
Aby uzyskać bardziej szczegółową dyskusję, zobacz tę dyskusję dotyczącą StackOverflow: How to use „while czytaj „(Bash), aby przeczytać ostatnią linię w pliku, jeśli nie ma nowej linii na końcu pliku?
Uwaga: to podejście dodaje dodatkowy znak nowej linii do ostatniej linii, jeśli jest brak.
Komentarze
- Niezły chwyt, dziękuję! Zwróć uwagę na " Uważaj, że dodaje to dodatkowy znak nowej linii w EOF, jeśli już go nie ma. " komentarz chociaż
- Tobiaszu, ja ' dodam to jako notatkę, dziękuję.
Odpowiedź
Z for
i IFS :
#!/bin/bash IFS=$"\n" # make newlines the only separator set -f # disable globbing for i in $(cat < "$1"); do echo "tester: $i" done
Należy jednak pamiętać, że pominie puste wiersze, ponieważ nowa linia to znak IFS-biała spacja, sekwencje z tego liczy się jako 1, a wiodące i końcowe są ignorowane. Za pomocą zsh
i ksh93
(nie bash
) możesz zmienić to na IFS=$"\n\n"
aby znak nowej linii nie był traktowany specjalnie, należy jednak pamiętać, że wszystkie końcowe znaki nowej linii (w tym końcowe puste wiersze) będą zawsze usuwane przez podstawienie polecenia.
Lub z read
(koniec z cat
):
#!/bin/bash while IFS= read -r line; do echo "tester: $line" done < "$1"
Tam są zachowywane puste wiersze, ale pamiętaj, że pominie on ostatni wiersz, jeśli nie został odpowiednio oddzielony znakiem nowego wiersza.
Komentarze
- dziękuję, nie ' nie wiedziałem, że można
<
w całą pętlę. Chociaż teraz to ma sens, widziałem to - Widzę, że
IFS \ read -r line' in second example. Is really
IFS = `potrzebne? IMHO wystarczy powiedzieć:while read -r line; do echo "tester: $line"; done < "$1"
- @GrzegorzWierzowiecki
IFS=
wyłącza usuwanie początkowych i końcowych białych spacji. Zobacz Wwhile IFS= read..
, dlaczego IFS nie działa? - @BenMares Aby zapobiec globbingowi wyrażenia, które prawdopodobnie pojawiają się w czytanym tekście, od rozszerzenia do pasujących nazw plików. Spróbuj na przykład
printf '%s\n' '*o' 'bar' >afile; touch foo; IFS=$'\n'; for i in $(cat afile); do echo "$i"; done
. - A
while IFS= read -r line || [ "$line" ]; do
przetworzy końcową linię niepoprawnie oddzieloną znakiem nowej linii (ale zostanie dodany z powrotem).
Odpowiedź
Ale warto to zrobić że dość często i nigdy nie pamiętam dokładnego sposobu używania while IFS= read...
, więc zdefiniowałem następującą funkcję w moim profilu basha:
# iterate the line of a file and call input function iterlines() { (( $# < 2 )) && { echo "Usage: iterlines <File> <Callback>"; return; } local File=$1 local Func=$2 n=$(cat "$File" | wc -l) for (( i=1; i<=n; i++ )); do "$Func" "$(sed "${i}q;d" "$File")" done }
Ta funkcja najpierw określa liczbę wierszy w pliku, a następnie używa sed
do wyodrębniania wiersz po wierszu i przekazuje każdy wiersz jako pojedynczy argument ciągu do dowolnego podanego funkcjonować. Przypuszczam, że może to stać się naprawdę nieefektywne w przypadku dużych plików, ale jak dotąd nie stanowiło to dla mnie problemu (oczywiście sugestie, jak ulepszyć to powitanie).
Użycie jest całkiem przyjemne IMO:
>> cat example.txt # note the use of spaces, whitespace, etc. a/path This is a sentence. "wi\th quotes" $End >> iterlines example.txt echo # preserves quotes, $ and whitespace a/path This is a sentence. "wi\th quotes" $End >> x() { echo "$#"; }; iterlines example.txt x # line always passed as single input string 1 1 1 1 1