Řekněme, že mám tento soubor:
hello world hello world
Tento program
#!/bin/bash for i in $(cat $1); do echo "tester: $i" done
výstupy
tester: hello tester: world tester: hello tester: world
Chtěl bych mít for
iteruje na každém řádku samostatně, ignoruje však mezery, tj. poslední dva řádky by měly být nahrazeny
tester: hello world
Používáním uvozovek for i in "$(cat $1)";
má za následek i
přiřazení celého souboru najednou. Co bych měl změnit?
Odpovědět
(o 9 let později 🙂
Obě poskytnuté odpovědi by selhaly na souborech bez nového řádku na konci, tím se efektivně přeskočí poslední řádek, neprodukují se žádné chyby, povede to ke katastrofě (naučil způsobem :).
Nejlepší výstižné řešení, které jsem zatím našel, že „Just Works“ (v bash i sh):
while IFS="" read -r LINE || [ -n "${LINE}" ]; do echo "processing line: ${LINE}" done < /path/to/input/file.txt
Podrobnější diskusi najdete v této diskusi StackOverflow: Jak používat „while read „(Bash) pro čtení posledního řádku v souboru, pokud na konci souboru není nový řádek?
Pozor: tento přístup přidá do posledního řádku další nový řádek, pokud existuje zatím žádné.
Komentáře
- Pěkný úlovek, děkuji! Všimněte si " Nezapomeňte, že na EOF přidáte nový nový řádek, pokud již žádný neexistuje. " komentář ačkoli
- Tobiasi, ' přidám to jako poznámku, děkuji.
Odpověď
S for
a IFS :
#!/bin/bash IFS=$"\n" # make newlines the only separator set -f # disable globbing for i in $(cat < "$1"); do echo "tester: $i" done
Upozorňujeme však, že přeskočí prázdné řádky, protože nový řádek je znak IFS-white-space, sekvence z toho se počítá jako 1 a přední a koncové jsou ignorovány. Pomocí zsh
a ksh93
(ne bash
) jej můžete změnit na IFS=$"\n\n"
aby nový řádek nebyl zpracován speciálně, mějte však na paměti, že všechny koncové znaky nového řádku (takže zahrnují koncové prázdné řádky) budou vždy odstraněny nahrazením příkazu.
Nebo s read
(již cat
):
#!/bin/bash while IFS= read -r line; do echo "tester: $line" done < "$1"
Tam zůstanou zachovány prázdné řádky, ale mějte na paměti, že přeskočí poslední řádek, pokud nebyl řádně ohraničen znakem nového řádku.
Komentáře
- díky, nevěděl jsem ', že by člověk nemohl
<
do celé smyčky. I když to teď dává naprosto smysl, viděl jsem to - vidím
IFS \ read -r line' in second example. Is really
IFS = `potřebný? IMHO stačí říct:while read -r line; do echo "tester: $line"; done < "$1"
- @GrzegorzWierzowiecki
IFS=
vypíná odstraňování úvodních a koncových mezer. Viz Proč nemá systém IFS vwhile IFS= read..
žádný účinek? - @BenMares Chcete-li zabránit globování výrazy, které se případně objevují v textu, který čteme, se rozšíří na shodné názvy souborů. Zkuste například
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
zpracuje koncový řádek, který není správně ohraničen znakem nového řádku (ale bude přidán zpět).
Odpověď
Aby to stálo za to, musím udělat to docela často a nikdy si nepamatuji přesný způsob použití while IFS= read...
, proto jsem ve svém bash profilu definoval následující funkci:
# 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 }
Tato funkce nejprve určí počet řádků v souboru, poté pomocí sed
extrahuje řádek po řádku a předá každý řádek jako jeden řetězcový argument libovolnému danému funkce. Předpokládám, že by to u velkých souborů mohlo být opravdu neúčinné, ale to pro mě dosud nebyl problém (samozřejmě návrhy, jak toto uvítání vylepšit).
Využití je docela sladké 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