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.
użytkownik192118
źródło
powiązane: stackoverflow.com/questions/9084257/…
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

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!
Radu Rădeanu
źródło
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ą.

zła
źródło
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.

Mitchell Currie
źródło
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>`"
Dandalf
źródło
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ą).

mik
źródło
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).
bac0n
źródło
-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 '*'
odoncaoa
źródło
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