Door de inhoud van een bestand in Bash bladeren

Hoe doorloop ik elke regel van een tekstbestand met Bash?

Met dit script:

echo "Start!"
for p in (peptides.txt)
do
    echo "${p}"
done

Ik krijg deze uitvoer op het scherm:

Start!
./runPep.sh: line 3: syntax error near unexpected token `('
./runPep.sh: line 3: `for p in (peptides.txt)'

(Later wil ik iets ingewikkelders doen met $p dan alleen naar het scherm te sturen.)


De omgevingsvariabele SHELL is (van env):

SHELL=/bin/bash

/bin/bash --version uitvoer:

GNU bash, version 3.1.17(1)-release (x86_64-suse-linux-gnu)
Copyright (C) 2005 Free Software Foundation, Inc.

cat /proc/version uitvoer:

Linux version 2.6.18.2-34-default (geeko@buildhost) (gcc version 4.1.2 20061115 (prerelease) (SUSE Linux)) #1 SMP Mon Nov 27 11:46:27 UTC 2006

Het bestand peptides.txt bevat:

RKEKNVQ
IPKKLLQK
QYFHQLEKMNVK
IPKKLLQK
GDLSTALEVAIDCYEK
QYFHQLEKMNVKIPENIYR
RKEKNVQ
VLAKHGKLQDAIN
ILGFMK
LEDVALQILL

Antwoord 1, autoriteit 100%

Een manier om dit te doen is:

while read p; do
  echo "$p"
done <peptides.txt

Zoals aangegeven in de opmerkingen, heeft dit de neveneffecten van het inkorten van voorlopende witruimte, het interpreteren van backslash-reeksen en het overslaan van de laatste regel als er een afsluitende regelinvoer ontbreekt. Als u zich zorgen maakt, kunt u het volgende doen:

while IFS="" read -r p || [ -n "$p" ]
do
  printf '%s\n' "$p"
done < peptides.txt

Bij wijze van uitzondering, als de loop body kan lezen van standaard invoer , u kunt het bestand openen met een andere bestandsdescriptor:

while read -u 10 p; do
  ...
done 10<peptides.txt

Hier is 10 slechts een willekeurig getal (anders dan 0, 1, 2).


Antwoord 2, autoriteit 24%

cat peptides.txt | while read line 
do
   # do something with $line here
done

en de oneliner-variant:

cat peptides.txt | while read line; do something_with_$line_here; done

Met deze opties wordt de laatste regel van het bestand overgeslagen als er geen regelinvoer aan het einde is.

U kunt dit als volgt vermijden:

cat peptides.txt | while read line || [[ -n $line ]];
do
   # do something with $line here
done

Antwoord 3, autoriteit 6%

Optie 1a: While-lus: één regel tegelijk: invoeromleiding

#!/bin/bash
filename='peptides.txt'
echo Start
while read p; do 
    echo $p
done < $filename

Optie 1b: While-lus: één regel tegelijk:
Open het bestand, lees uit een bestandsdescriptor (in dit geval bestandsdescriptor #4).

#!/bin/bash
filename='peptides.txt'
exec 4<$filename
echo Start
while read -u4 p ; do
    echo $p
done

Antwoord 4, autoriteit 4%

Dit is niet beter dan andere antwoorden, maar het is nog een manier om de klus te klaren in een bestand zonder spaties (zie opmerkingen). Ik merk dat ik vaak oneliners nodig heb om door lijsten in tekstbestanden te bladeren zonder de extra stap om aparte scriptbestanden te gebruiken.

for word in $(cat peptides.txt); do echo $word; done

Met dit formaat kan ik alles in één opdrachtregel plaatsen. Verander het “echo $woord” gedeelte in wat je maar wilt en je kunt meerdere commando’s geven, gescheiden door puntkomma’s. Het volgende voorbeeld gebruikt de inhoud van het bestand als argumenten in twee andere scripts die u mogelijk hebt geschreven.

for word in $(cat peptides.txt); do cmd_a.sh $word; cmd_b.py $word; done

Of als u van plan bent dit als een stream-editor te gebruiken (leer sed), kunt u de uitvoer als volgt naar een ander bestand dumpen.

for word in $(cat peptides.txt); do cmd_a.sh $word; cmd_b.py $word; done > outfile.txt

Ik heb deze zoals hierboven beschreven gebruikt omdat ik tekstbestanden heb gebruikt waar ik ze heb gemaakt met één woord per regel. (Zie opmerkingen) Als je spaties hebt waarvan je je woorden/regels niet wilt splitsen, wordt het een beetje lelijker, maar hetzelfde commando werkt nog steeds als volgt:

OLDIFS=$IFS; IFS=$'\n'; for line in $(cat peptides.txt); do cmd_a.sh $line; cmd_b.py $line; done > outfile.txt; IFS=$OLDIFS

Dit vertelt de shell alleen maar om alleen op nieuwe regels te splitsen, niet op spaties, en keert vervolgens de omgeving terug naar wat het eerder was. Op dit punt kunt u echter overwegen om alles in een shellscript te plaatsen in plaats van alles in een enkele regel te persen.

Veel succes!


Antwoord 5, autoriteit 4%

Nog een paar dingen die niet worden gedekt door andere antwoorden:

Lezen uit een bestand met scheidingstekens

# ':' is the delimiter here, and there are three fields on each line in the file
# IFS set below is restricted to the context of `read`, it doesn't affect any other code
while IFS=: read -r field1 field2 field3; do
  # process the fields
  # if the line has less than three fields, the missing fields will be set to an empty string
  # if the line has more than three fields, `field3` will get all the values, including the third field plus the delimiter(s)
done < input.txt

Lezen van de uitvoer van een ander commando, met behulp van procesvervanging

while read -r line; do
  # process the line
done < <(command ...)

Deze aanpak is beter dan command ... | while read -r line; do ... omdat de while-lus hier in de huidige shell wordt uitgevoerd in plaats van een subshell zoals in het geval van de laatste. Zie het gerelateerde bericht Een variabele die is gewijzigd in een while-lus is niet onthouden.

Lezen van een null-gescheiden invoer, bijvoorbeeld find ... -print0

while read -r -d '' line; do
  # logic
  # use a second 'read ... <<< "$line"' if we need to tokenize the line
done < <(find /path/to/dir -print0)

Gerelateerde lees: BashFAQ/020 – Hoe kan ik bestandsnamen met nieuwe regels, spaties of beide vinden en veilig verwerken ?

Lezen uit meer dan één bestand tegelijk

while read -u 3 -r line1 && read -u 4 -r line2; do
  # process the lines
  # note that the loop will end when we reach EOF on either of the files, because of the `&&`
done 3< input1.txt 4< input2.txt

Gebaseerd op @chepner’s antwoord hier:

-u is een bash-extensie. Voor POSIX-compatibiliteit zou elke aanroep er ongeveer zo uitzien als read -r X <&3.

Een heel bestand in een array lezen (Bash-versies eerder tot 4)

while read -r line; do
    my_array+=("$line")
done < my_file

Als het bestand eindigt met een onvolledige regel (nieuwe regel ontbreekt aan het einde), dan:

while read -r line || [[ $line ]]; do
    my_array+=("$line")
done < my_file

Een heel bestand in een array lezen (Bash-versies 4x en hoger)

readarray -t my_array < my_file

of

mapfile -t my_array < my_file

En dan

for line in "${my_array[@]}"; do
  # process the lines
done

Gerelateerde berichten:


Antwoord 6, autoriteit 2%

Gebruik een while-lus, zoals deze:

while IFS= read -r line; do
   echo "$line"
done <file

Opmerkingen:

  1. Als u de IFS niet correct instelt, verliest u de inspringing.

  2. Je moet bijna altijd de -r optie gebruiken met read.

  3. Lees geen regels met for


Antwoord 7

Stel dat je dit bestand hebt:

$ cat /tmp/test.txt
Line 1
    Line 2 has leading space
Line 3 followed by blank line
Line 5 (follows a blank line) and has trailing space    
Line 6 has no ending CR

Er zijn vier elementen die de betekenis van de bestandsuitvoer die door veel Bash-oplossingen wordt gelezen, zullen veranderen:

  1. De lege regel 4;
  2. Voor- of volgspaties op twee regels;
  3. De betekenis van individuele regels behouden (d.w.z. elke regel is een record);
  4. De regel 6 eindigt niet met een CR.

Als je het tekstbestand regel voor regel wilt, inclusief lege regels en afsluitende regels zonder CR, moet je een while-lus gebruiken en moet je een alternatieve test hebben voor de laatste regel.

Hier zijn de methoden die het bestand kunnen wijzigen (in vergelijking met wat cat retourneert):

1) De laatste regel en voorloop- en volgspaties kwijtraken:

$ while read -r p; do printf "%s\n" "'$p'"; done </tmp/test.txt
'Line 1'
'Line 2 has leading space'
'Line 3 followed by blank line'
''
'Line 5 (follows a blank line) and has trailing space'

(Als u while IFS= read -r p; do printf "%s\n" "'$p'"; done </tmp/test.txt doet, behoudt u de voorloop- en volgspaties maar verliest nog steeds de laatste regel als deze niet wordt afgesloten met CR)

2) Als u procesvervanging gebruikt met cat, wordt het hele bestand in één keer gelezen en verliest u de betekenis van afzonderlijke regels:

$ for p in "$(cat /tmp/test.txt)"; do printf "%s\n" "'$p'"; done
'Line 1
    Line 2 has leading space
Line 3 followed by blank line
Line 5 (follows a blank line) and has trailing space    
Line 6 has no ending CR'

(Als je de " uit $(cat /tmp/test.txt) verwijdert, lees je het bestand woord voor woord in plaats van één slok. Ook waarschijnlijk niet wat is bedoeld…)


De meest robuuste en eenvoudigste manier om een ​​bestand regel voor regel te lezen en alle spaties te behouden is:

$ while IFS= read -r line || [[ -n $line ]]; do printf "'%s'\n" "$line"; done </tmp/test.txt
'Line 1'
'    Line 2 has leading space'
'Line 3 followed by blank line'
''
'Line 5 (follows a blank line) and has trailing space    '
'Line 6 has no ending CR'

Als u voorloop- en handelsruimten wilt verwijderen, verwijdert u het IFS= gedeelte:

$ while read -r line || [[ -n $line ]]; do printf "'%s'\n" "$line"; done </tmp/test.txt
'Line 1'
'Line 2 has leading space'
'Line 3 followed by blank line'
''
'Line 5 (follows a blank line) and has trailing space'
'Line 6 has no ending CR'

(Een tekstbestand zonder een afsluitende \n, hoewel vrij gebruikelijk, wordt onder POSIX als verbroken beschouwd. Als u kunt rekenen op de achterliggende \n, hoeft u geen || [[ -n $line ]] in de while-lus.)

Meer op de Veelgestelde vragen over BASH


Antwoord 8

Als je niet wilt dat je leesregel wordt onderbroken door een teken van een nieuwe regel, gebruik dan –

#!/bin/bash
while IFS='' read -r line || [[ -n "$line" ]]; do
    echo "$line"
done < "$1"

Voer vervolgens het script uit met de bestandsnaam als parameter.


Antwoord 9

#!/bin/bash
#
# Change the file name from "test" to desired input file 
# (The comments in bash are prefixed with #'s)
for x in $(cat test.txt)
do
    echo $x
done

Antwoord 10

Ik gebruik graag xargs in plaats van while. xargs is krachtig en commandoregelvriendelijk

cat peptides.txt | xargs -I % sh -c "echo %"

Met xargs kunt u ook breedsprakigheid toevoegen met -t en validatie met -p


Antwoord 11

Hier is mijn voorbeeld uit het echte leven hoe lijnen van een ander programma-uitvoer te herhalen, te controleren op substrings, dubbele aanhalingstekens van variabele te verwijderen en die variabele buiten de lus te gebruiken. Ik denk dat heel veel mensen deze vragen vroeg of laat stellen.

##Parse FPS from first video stream, drop quotes from fps variable
## streams.stream.0.codec_type="video"
## streams.stream.0.r_frame_rate="24000/1001"
## streams.stream.0.avg_frame_rate="24000/1001"
FPS=unknown
while read -r line; do
  if [[ $FPS == "unknown" ]] && [[ $line == *".codec_type=\"video\""* ]]; then
    echo ParseFPS $line
    FPS=parse
  fi
  if [[ $FPS == "parse" ]] && [[ $line == *".r_frame_rate="* ]]; then
    echo ParseFPS $line
    FPS=${line##*=}
    FPS="${FPS%\"}"
    FPS="${FPS#\"}"
  fi
done <<< "$(ffprobe -v quiet -print_format flat -show_format -show_streams -i "$input")"
if [ "$FPS" == "unknown" ] || [ "$FPS" == "parse" ]; then 
  echo ParseFPS Unknown frame rate
fi
echo Found $FPS

Declareer variabele buiten de lus, stel de waarde in en gebruik deze buiten de lus vereist gedaan <<< “$(…)” syntaxis. De toepassing moet worden uitgevoerd binnen een context van de huidige console. Aanhalingstekens rond de opdracht behouden nieuwe regels van de uitvoerstroom.

Loopovereenkomst voor substrings leest dan name=value paar, splitst rechtergedeelte van laatste = teken, laat eerste aanhalingsteken vallen, laat laatste aanhalingsteken vallen, we hebben een schone waarde die elders kan worden gebruikt.


Antwoord 12

Dit is misschien het eenvoudigste antwoord en misschien werkt het niet in alle gevallen, maar voor mij werkt het prima:

while read line;do echo "$line";done<peptides.txt

als u tussen haakjes moet plaatsen voor spaties:

while read line;do echo \"$line\";done<peptides.txt

Ahhh, dit is ongeveer hetzelfde als het antwoord dat de meeste stemmen kreeg, maar het staat allemaal op één regel.


Antwoord 13

subshell is een probleem bij gebruik van pipe.

while read line 
do
  # do something with $line here
done <<<$(cat peptides.txt)

Antwoord 14

@Peter: Dit zou voor jou kunnen werken-

echo "Start!";for p in $(cat ./pep); do
echo $p
done

Dit zou de output teruggeven-

Start!
RKEKNVQ
IPKKLLQK
QYFHQLEKMNVK
IPKKLLQK
GDLSTALEVAIDCYEK
QYFHQLEKMNVKIPENIYR
RKEKNVQ
VLAKHGKLQDAIN
ILGFMK
LEDVALQILL

Antwoord 15

Dit komt nogal laat, maar met de gedachte dat het iemand kan helpen, voeg ik het antwoord toe. Dit is misschien ook niet de beste manier. De opdracht head kan worden gebruikt met het argument -n om n regels te lezen vanaf het begin van het bestand en eveneens tail commando kan worden gebruikt om van onderaf te lezen. Om nu nde regel uit het bestand op te halen, gaan we naar n regels, pijpen de gegevens naar slechts 1 regel van de doorgesluisde gegevens.

   TOTAL_LINES=`wc -l $USER_FILE | cut -d " " -f1 `
   echo $TOTAL_LINES       # To validate total lines in the file
   for (( i=1 ; i <= $TOTAL_LINES; i++ ))
   do
      LINE=`head -n$i $USER_FILE | tail -n1`
      echo $LINE
   done

LEAVE A REPLY

Please enter your comment!
Please enter your name here

fifteen + thirteen =

Other episodes