Si at jeg har denne filen:
hello world hello world
Dette programmet
#!/bin/bash for i in $(cat $1); do echo "tester: $i" done
utganger
tester: hello tester: world tester: hello tester: world
Jeg vil gjerne ha for
gjentas over hver linje individuelt og ignorerer imidlertid mellomrommene, dvs. de to siste linjene bør erstattes av
tester: hello world
Bruk av anførselstegn for i in "$(cat $1)";
resulterer i at i
tildeles hele filen på en gang. Hva skal jeg endre?
Svar
(9 år senere 🙂
Begge gitt svarene mislyktes på filer uten en ny linje på slutten, dette vil effektivt hoppe over den siste linjen, gi ingen feil, vil føre til katastrofe (lærte hardt måte :).
Den beste konsise løsningen jeg har funnet så langt 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
For mer grundig diskusjon, se denne StackOverflow-diskusjonen: Hvordan bruke «mens les «(Bash) for å lese den siste linjen i en fil hvis det ikke er noen ny linje på slutten av filen?
Vær forsiktig: denne tilnærmingen legger til en ekstra ny linje til den siste linjen hvis det er ingen allerede.
Kommentarer
- Fin fangst, takk! Legg merke til " Vær oppmerksom på at dette legger til en ekstra ny linje på EOF hvis det ikke allerede er noen. " kommentar skjønt
- Tobias, jeg ' Jeg legger til dette som et notat, takk.
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
Merk imidlertid at det vil hoppe over tomme linjer som ny linje som et IFS-hvitt mellomromstegn, sekvenser av det teller som 1 og de ledende og etterfølgende blir ignorert. Med zsh
og ksh93
(ikke bash
) kan du endre det til IFS=$"\n\n"
for ny linje som ikke skal behandles spesielt, men vær oppmerksom på at alle etterfølgende nye linjetegn (slik at de inkluderer etterfølgende tomme linjer) alltid vil bli fjernet med kommandosubstitusjonen.
Eller med read
(ikke mer cat
):
#!/bin/bash while IFS= read -r line; do echo "tester: $line" done < "$1"
Der er tomme linjer bevart, men merk at det vil hoppe over den siste linjen hvis den ikke ble avgrenset ordentlig av et nytt linjetegn.
Kommentarer
- takk, jeg visste ikke ' jeg kunne
<
i en hel løkke. Selv om det er helt fornuftig nå så jeg det - Jeg ser
IFS \ read -r line' in second example. Is really
IFS = `nødvendig? IMHO er det nok å si:while read -r line; do echo "tester: $line"; done < "$1"
- @GrzegorzWierzowiecki
IFS=
slår av stripping av ledende og etterfølgende mellomrom. Se Iwhile IFS= read..
, hvorfor har IFS ingen effekt? - @BenMares For å forhindre globbing uttrykk som muligens vises i teksten vi leser fra utvides til samsvarende filnavn. Prøv for eksempel
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
vil behandle en etterfølgende linje som ikke er riktig avgrenset av et nytt linjetegn (men det vil bli lagt tilbake).
Svar
For hva det er verdt, må jeg gjøre det ganske ofte, og kan aldri huske den eksakte måten å bruke while IFS= read...
, så jeg definerte følgende funksjon 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 funksjonen bestemmer først antall linjer i filen, bruker deretter sed
for å trekke ut linje etter linje, og sender hver linje som et enkeltstrengargument til et gitt funksjon. Jeg antar at dette kan bli veldig ineffektivt med store filer, men det har ikke vært et problem for meg så langt (forslag til hvordan du kan forbedre denne velkomst selvfølgelig).
Bruken er ganske søt 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