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.