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