web-dev-qa-db-fra.com

Lire une ligne de fichier par ligne et si la condition est remplie, continuez à lire jusqu'à la condition suivante

J'ai un fichier foo.txt

test
qwe
asd
xca
asdfarrf
sxcad
asdfa
sdca
dac
dacqa
ea
sdcv
asgfa
sdcv
ewq
qwe
a
df
fa
vas
fg
fasdf
eqw
qwe
aefawasd
adfae
asdfwe
asdf
era
fbn
tsgnjd
nuydid
hyhnydf
gby
asfga
dsg
eqw
qwe
rtargt
raga
adfgasgaa
asgarhsdtj
shyjuysy
sdgh
jstht
ewq
sdtjstsa
sdghysdmks
aadfbgns,
asfhytewat
bafg
q4t
qwe
asfdg5ab
fgshtsadtyh
wafbvg
nasfga
ghafg
ewq
qwe
afghta
asg56ang
adfg643
5aasdfgr5
asdfg
fdagh5t
ewq

Je veux imprimer toutes les lignes entre qwe et ewq dans un fichier séparé. C'est ce que j'ai jusqu'à présent:

#!/bin/bash

filename="foo.txt"

#While loop to read line by line
while read -r line
do
    readLine=$line
    #If the line starts with ST then echo the line
    if [[ $readLine = qwe* ]] ; then
        echo "$readLine"
        read line
        readLine=$line
        if [[ $readLine = ewq* ]] ; then
            echo "$readLine"
        fi
    fi
done < "$filename"
4
gkmohit

Vous devez apporter des modifications à votre script (sans ordre particulier):

  • Utilisation IFS= Avant read _ Pour éviter d'éliminer les espaces de dirigeants et de fin.
  • Comme $line n'est pas changé nulle part, il n'y a pas besoin de variable readLine.
  • N'utilisez pas de lecture au milieu de la boucle !!.
  • Utilisez une variable booléenne pour contrôler l'impression.
  • Faire clairement le début et la fin de l'impression.

Avec ces changements, le script devient:

#!/bin/bash

filename="foo.txt"

#While loop to read line by line
while IFS= read -r line; do
    #If the line starts with ST then set var to yes.
    if [[ $line == qwe* ]] ; then
        printline="yes"
        # Just t make each line start very clear, remove in use.
        echo "----------------------->>"
    fi
    # If variable is yes, print the line.
    if [[ $printline == "yes" ]] ; then
        echo "$line"
    fi
    #If the line starts with ST then set var to no.
    if [[ $line == ewq* ]] ; then
        printline="no"
        # Just to make each line end very clear, remove in use.
        echo "----------------------------<<"
    fi
done < "$filename"

Qui pourrait être condensé de cette manière:

#!/bin/bash
filename="foo.txt"
while IFS= read -r line; do
    [[ $line == qwe* ]]       && printline="yes"
    [[ $printline == "yes" ]] && echo "$line"
    [[ $line == ewq* ]]       && printline="no"
done < "$filename"

Qui imprimera les lignes de début et de fin (inclus).
[.____] S'il n'y a pas besoin de les imprimer, échangez les tests de début et de fin:

#!/bin/bash
filename="foo.txt"
while IFS= read -r line; do
    [[ $line == ewq* ]]       && printline="no"
    [[ $printline == "yes" ]] && echo "$line"
    [[ $line == qwe* ]]       && printline="yes"
done < "$filename"

Cependant, ce serait tout à fait meilleur (si vous avez Bash version 4.0 ou mieux) utiliser readarray et boucle avec les éléments de la matrice:

#!/bin/dash
filename="infile"

readarray -t lines < "$filename"


for line in "${lines[@]}"; do
    [[ $line == ewq* ]]       && printline="no"
    [[ $printline == "yes" ]] && echo "$line"
    [[ $line == qwe* ]]       && printline="yes"
done

Cela évitera la plupart des problèmes d'utilisation read.


Bien sûr, vous pouvez utiliser les recommandations (en commentaires; merci, @costas) sed ligne Pour obtenir uniquement les lignes à traiter:

    #!/bin/bash
filename="foo.txt"

readarray -t lines <<< "$(sed -n '/^qwe.*/,/^ewq.*/p' "$filename")"

for line in "${lines[@]}"; do

     : # Do all your additional processing here, with a clean input.

done 
7
user79743

Comme @Costas a souligné, l'outil correct à utiliser pour ce travail est sed:

sed '/qwe/,/ewq/ w other.file' foo.txt

Il peut y avoir autre traitement nécessaire sur les lignes à imprimer. C'est très bien; faites-le comme si:

sed -e '/qwe/,/ewq/{w other.file' -e 'other processing;}' foo.txt

(Bien sûr, "Autre traitement" n'est pas une vraie commande sed.) Ce qui précède est le modèle à utiliser si vous devez faire votre traitement après Vous imprimez la ligne. Si vous souhaitez effectuer un autre traitement, puis imprimer une version modifiée de la ligne (qui semble plus probable), vous utiliseriez:

sed -e '/qwe/,/ewq/{processing;w other.file' -e '}' foo.txt

(Notez qu'il est nécessaire de mettre le } dans son propre argument, sinon il sera interprété comme faisant partie de la other.file Nom.)


Vous n'avez pas indiqué que "autre traitement" que vous devez faire sur les lignes ou que je pourrais être plus précis. Mais quel que soit ce que le traitement est, vous pouvez certainement le faire dans sed- ou si cela devient trop lourd, vous pouvez le faire dans awk avec très peu de changement au code ci-dessus:

awk '/qwe/,/ewq/ { print > "other.file" }' foo.txt

Ensuite, vous avez toute la puissance du langage de programmation awk à votre disposition pour le traitement sur les lignes avant d'exécuter cette déclaration print. Et bien sûr awk (et sed) sont conç pour le traitement de texte, contrairement à bash.

4
Wildcard
qwe(){ printf %s\\n "$1"; }
ewq(){ :; }
IFS=   ### prep  the  loop, only IFS= once
while  read -r  in
do     case $in in
       (qwe|ewq)
           set "$in"
       ;;
       ("$processing"?)
           "$process"
       esac
       "$1" "$in"
done

C'est une façon très lente de le faire. Avec un GNU grep et un régulier infile:

IFS=
while grep  -xm1 qwe
do    while read  -r  in  &&
            [ ewq != "$in" ]
      do    printf %s\\n "$in"
            : some processing
      done
done <infile

... au moins optimiserait la moitié des lectures inefficaces ...

sed  -ne '/^qwe$/,/^ewq$/H;$!{/^qwe$/!d;}' \
      -e "x;s/'"'/&\\&&/g;s/\n/'"' '/g"    \
      -e "s/\(.*\) .e.*/p '\1/p" <input    |
sh    -c 'p(){  printf %s\\n "$@"
                for l do : process "$l"
                done
          }; . /dev/fd/0'

Et cela éviterait les inefficacités de read tout à fait pour la plupart des sh 'sort là-bas, bien qu'il ait à imprimer deux fois la sortie - une fois citée à sh et une fois non noté à STDOUT. Cela fonctionne différemment parce que le . La commande a tendance à lire l'entrée par bloc plutôt que par octet pour la plupart des implémentations. Néanmoins, il s'éloyait tout à fait de l'EWQ - QWe et fonctionnerait pour une contribution en continu - comme une FIFO.

qwe
asd
xca
asdfarrf
sxcad
asdfa
sdca
dac
dacqa
ea
sdcv
asgfa
sdcv
qwe
a
df
fa
vas
fg
fasdf
qwe
aefawasd
adfae
asdfwe
asdf
era
fbn
tsgnjd
nuydid
hyhnydf
gby
asfga
dsg
qwe
rtargt
raga
adfgasgaa
asgarhsdtj
shyjuysy
sdgh
jstht
qwe
asfdg5ab
fgshtsadtyh
wafbvg
nasfga
ghafg
qwe
afghta
asg56ang
adfg643
5aasdfgr5
asdfg
fdagh5t
1
mikeserv