Sig, at jeg har denne fil:
hello world hello world
Dette program
#!/bin/bash for i in $(cat $1); do echo "tester: $i" done
output
tester: hello tester: world tester: hello tester: world
Jeg vil gerne have for
gentages over hver linje individuelt og ignorerer dog mellemrum, dvs. de to sidste linjer skal erstattes af
tester: hello world
Brug af anførselstegn for i in "$(cat $1)";
resulterer i, at i
tildeles hele filen på én gang. Hvad skal jeg ændre?
Svar
(9 år senere 🙂
Begge forudsatte svar ville mislykkes på filer uden en ny linje i slutningen, dette springer effektivt over den sidste linje, giver ingen fejl, vil føre til katastrofe (lært hårdt måde :).
Den bedste koncise løsning, jeg hidtil har fundet, at “Just Works” (i både bash og sh):
while IFS="" read -r LINE || [ -n "${LINE}" ]; do echo "processing line: ${LINE}" done < /path/to/input/file.txt
Se denne StackOverflow-diskussion for mere dybtgående diskussion: Sådan bruges “mens læs “(Bash) for at læse den sidste linje i en fil, hvis der ikke er nogen ny linje i slutningen af filen?
Pas på: denne tilgang tilføjer en ekstra ny linje til den sidste linje, hvis der er ingen allerede.
Kommentarer
- Dejlig fangst, tak! Bemærk " Pas på, at dette tilføjer en ekstra ny linje ved EOF, hvis der ikke allerede er nogen. " kommentar dog
- Tobias, jeg ' Jeg tilføjer dette som en note, tak.
Svar
Med for
og IFS :
#!/bin/bash IFS=$"\n" # make newlines the only separator set -f # disable globbing for i in $(cat < "$1"); do echo "tester: $i" done
Bemærk dog, at det springer tomme linjer over, da newline er et IFS-hvidt mellemrumstegn, sekvenser af det tæller som 1, og de førende og bageste ignoreres. Med zsh
og ksh93
(ikke bash
) kan du ændre det til IFS=$"\n\n"
for newline, der ikke skal behandles specielt, dog bemærk at alle trail newline-tegn (så det inkluderer tomme linier) altid fjernes ved kommandosubstitution.
Eller med read
(ikke mere cat
):
#!/bin/bash while IFS= read -r line; do echo "tester: $line" done < "$1"
Der bevares tomme linjer, men bemærk, at det springer den sidste linje over, hvis den ikke blev afgrænset korrekt med et nyt linjetegn.
Kommentarer
- tak, jeg vidste ikke ' jeg kunne ikke
<
i en hel løkke. Selvom det giver perfekt mening nu så jeg det - Jeg ser
IFS \ read -r line' in second example. Is really
IFS = `nødvendigt? IMHO er det nok at sige:while read -r line; do echo "tester: $line"; done < "$1"
- @GrzegorzWierzowiecki
IFS=
slukker for fjernelse af ledende og efterfølgende hvidt rum. Se Iwhile IFS= read..
, hvorfor har IFS ingen effekt? - @BenMares For at forhindre globbing udtryk, der muligvis vises i teksten, vi læser, fra at blive udvidet til matchende filnavne. Prøv f.eks.
printf '%s\n' '*o' 'bar' >afile; touch foo; IFS=$'\n'; for i in $(cat afile); do echo "$i"; done
. - En
while IFS= read -r line || [ "$line" ]; do
behandler en efterfølgende linje, der ikke er korrekt afgrænset af et nyt linjetegn (men det tilføjes tilbage).
Svar
For hvad det er værd, skal jeg gøre det ganske ofte og kan aldrig huske den nøjagtige måde at bruge while IFS= read...
på, så jeg definerede følgende funktion i min bash-profil:
# 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 }
Denne funktion bestemmer først antallet af linjer i filen og bruger derefter sed
til at udtrække linje efter linje og sender hver linje som et enkeltstrengargument til et givet fungere. Jeg formoder, at dette måske bliver virkelig ineffektivt med store filer, men det har ikke været et problem for mig hidtil (forslag til, hvordan man forbedrer denne velkomst selvfølgelig).
Brugen er ret sød 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