Jak czytać pełną linię w pętli „for” ze spacjami


128

Próbuję uruchomić forpętlę dla pliku i chcę wyświetlić całą linię. Zamiast tego wyświetla tylko ostatnie słowo. Chcę pełną linię.

for j in `cat ./file_wget_med`

do
echo $j

done

wynik po uruchomieniu:

Found.

Oto moje dane:

$ cat file_wget_med
2013-09-11 14:27:03 ERROR 404: Not Found.

Odpowiedzi:


178

forPętla dzieli się, gdy widzi spacje, takie jak spacja, tabulator lub znak nowej linii. Dlatego powinieneś użyć IFS (Internal Field Separator) :

IFS=$'\n'       # make newlines the only separator
for j in $(cat ./file_wget_med)    
do
    echo "$j"
done
# Note: IFS needs to be reset to default!

5
I dbaj o pojedyncze cudzysłowy w IFS, ponieważ IFS = $ „\ n” podzieli również ciągi „nn”. Odkryłem, że zestaw IFS jest bardziej niezawodny niż stosowanie bardziej skomplikowanej składni lub funkcji.
erm3nda,

23
najwyraźniej zaleca się unset IFSpóźniej, aby nie wpływać na inne polecenia w tej samej powłoce.
Boris Däppen

2
Co to $znaczy IFS=$'\n'?
Ren

2
$sprawia, że ​​podziały wierszy działają wewnątrz ciągu. Należy to również zrobić za local IFS=$'\n'pomocą funkcji.
Andy Ray

1
@Renn, który jest $'...'znany „ ANSI-C
Quoting

82

forpętle domyślnie dzielą się na dowolne białe znaki (spację, tabulator, znak nowej linii); najłatwiejszym sposobem pracy na jednej linii na raz jest użycie while readpętli, która dzieli się na nowe linie:

while read i; do echo "$i"; done < ./file_wget_med

Chciałbym spodziewać Twój komenda wypluć jedno słowo na linię (to co się stało, gdy testowałem go z plikiem własną rękę). Jeśli dzieje się coś innego, nie jestem pewien, co może być przyczyną.


7
Jest to także dobra alternatywa for i in `ls`; do echo $1; done, za potokiem wyjście do polecenia while: ls|while read i; do echo $i; done. Działa to na nazwach plików / folderów ze spacjami, bez potrzeby zmiany zmiennych środowiskowych. Bardzo dobra odpowiedź.
jishi

podczas gdy nie obsługiwał bardzo pierwszej i ostatniej linii, nie tak dobrze, jak pętli for z IFS
grantbow

@jishi Ponieważ jest przeznaczony przede wszystkim do wyświetlania i reaguje na rozmiar okna terminala, i dlatego lsnie jest uważany za bezpieczny w użyciu |(operator potoku). findjest bardziej elastyczny poza tym, że jest w tym celu bezpieczniejszy.
SeldomNeedy

3
To jest WIELKA odpowiedź, użyłem jej do przekształcenia listy, którą miałem, w wpisy PLIST dla aplikacji IOS, którą tworzyłem na Mac OSX. Zamieniłem plik wejściowy, przesyłając plik clipboad za pomocą pbpaste ->pbpaste | while read t; do echo "<string>$t</string>"; done
Big Rich

3
ls -1może być używany w |celu zagwarantowania niesformatowanego wyjścia jeden na linię
AndreyS Scherbakov

19
#!/bin/bash
files=`find <subdir> -name '*'`
while read -r fname; do
    echo $fname
done <<< "$files"

Sprawdzona praca, nie ta jedna wkładka, której prawdopodobnie chcesz, ale nie jest to możliwe elegancko.


1
ciąg tutaj! najbardziej eleganckie ze wszystkich innych rozwiązań IMO
Eliran Malka

5

Oto niewielkie rozszerzenie odpowiedzi Mitchella Currie, które podoba mi się ze względu na niewielki zakres skutków ubocznych, co pozwala uniknąć konieczności ustawiania zmiennej:

#!/bin/bash
while read -r fname; do
    echo $fname
done <<< "`find <subdir>`"

1
Możesz usunąć -name '*'i byłoby tak samo, nie?
wjandrea

1

Chciałbym napisać tak:

cat ./file_wget_med | while read -r j
do
    echo $j
done

ponieważ wymaga najmniejszych zmian w oryginalnym skrypcie (z wyjątkiem rozwiązania używającego IFS, ale modyfikuje bashzachowanie nie tylko dla tej instrukcji sterowania pętlą).


1
Rurociągi i catsą tutaj niepotrzebne. whilepętla akceptuje przekierowanie w porządku, dlatego zwykle jest to napisane jakowhile IFS= read -r line; do...done < input.txt
Sergiy Kolodyazhnyy

0

Mapfile to wygodny sposób odczytu linii z pliku do tablicy indeksowanej, nie tak przenośny jak odczyt, ale nieco szybszy. Używając pętli for, unikniesz tworzenia podpowłoki.

#!/bin/bash

mapfile -t < file.txt

for line in "${MAPFILE[@]}"; do
    echo $line
done

Pamiętaj, że podczas korzystania z potoków umieści pętlę while w podpowłoce. Zmiany w zmiennych podobnych do pętli while nie będą się rozprzestrzeniać na zewnętrzną część skryptu.

Przykład:

#!/bin/bash

a=0
printf %s\\n {0..5} | while read; do
  ((a++))
done
echo $a # 'a' will always be 0.

(Lepsze rozwiązanie):

#!/bin/bash

b=0
while read; do
  ((b++))
done < <(printf %s\\n {0..5})

echo $b # 'b' equal to 6 (works as expected).

-1

Dandalf zbliżył się do rozwiązania funkcjonalnego, ale NIGDY nie należy próbować przypisywać wyniku nieznanych ilości danych wejściowych (tj. find ~/.gdfuse -name '*') Zmiennym! Lub przynajmniej próbuj zrobić coś takiego za pomocą zmiennej tablicowej; jeśli nalegacie na bycie tak leniwymi! Oto rozwiązanie Dandalfa wykonane bez niebezpiecznego manewru; i ogólnie w jednym wierszu

while read -r fname; do
  echo $fname;
done <<< `find ~/.gdfuse -name '*'

Hej @odoncaoa, próbowałem wykonać polecenie find bez podwójnych cudzysłowów, ale powoduje to, że wszystkie wyniki są wyświetlane jako jedna linia. Jestem zdezorientowany co do „nieznanych ilości danych wejściowych” do tego polecenia i związanych z tym niebezpieczeństw. Czy możesz podać przykład niebezpieczeństw i ich teoretyczne skutki? Poważnie nie chcę się o to lenić, po prostu czuję się swobodniej w innych językach programowania.
Dandalf
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.