GNU/Linux >> Tutoriels Linux >  >> Linux

Comment sélectionner plusieurs lignes à partir d'un fichier ou d'un tube dans un script ?

Vous pouvez utiliser ce awk :

awk -v s='2,4' 'BEGIN{split(s, a, ","); for (i in a) b[a[i]]} NR in b' file
two
four

Via un script séparé lines.sh :

#!/bin/bash
awk -v s="$1" 'BEGIN{split(s, a, ","); for (i in a) b[a[i]]} NR in b' "$2"

Donnez ensuite les autorisations d'exécution :

chmod +x lines.sh

Et appelez-le comme :

./lines.sh '2,4' 'test.txt'

Essayez sed :

sed -n '2p; 4p' inputFile

-n dit sed pour supprimer la sortie, mais pour les lignes 2 et 4 , le p La commande (print) est utilisée pour imprimer ces lignes.

Vous pouvez également utiliser des plages, par exemple :

sed -n '2,4p' inputFile

Deux versions pures de Bash. Puisque vous recherchez des solutions générales et réutilisables, autant y mettre un peu d'effort. (Voir également la dernière section).

Version 1

Ce script transforme tout le stdin dans un tableau (en utilisant mapfile , donc c'est plutôt efficace) puis imprime les lignes spécifiées sur ses arguments. Les plages sont valides, par exemple,

1-4 # for lines 1, 2, 3 and 4
3-  # for everything from line 3 till the end of the file

Vous pouvez les séparer par des espaces ou des virgules. Les lignes sont imprimées exactement dans l'ordre dans lequel les arguments sont donnés :

lines 1 1,2,4,1-3,4- 1

imprimera la ligne 1 deux fois, puis la ligne 2, puis la ligne 4, puis les lignes 1, 2 et 3, puis tout de la ligne 4 jusqu'à la fin, et enfin, la ligne 1 à nouveau.

Voilà :

#!/bin/bash

lines=()

# Slurp stdin in array
mapfile -O1 -t lines

# Arguments:
IFS=', ' read -ra args <<< "$*"

for arg in "${args[@]}"; do
   if [[ $arg = +([[:digit:]]) ]]; then
      arg=$arg-$arg
   fi
   if [[ $arg =~ ([[:digit:]]+)-([[:digit:]]*) ]]; then
      ((from=10#${BASH_REMATCH[1]}))
      ((to=10#${BASH_REMATCH[2]:-$((${#lines[@]}))}))
      ((from==0)) && from=1
      ((to>=${#lines[@]})) && to=${#lines[@]}
      ((from<=to)) || printf >&2 'Argument %d-%d: lines not in increasing order' "$from" "$to"
      for((i=from;i<=to;++i)); do
         printf '%s\n' "${lines[i]}"
      done
   else
      printf >&2 "Error in argument \`%s'.\n" "$arg"
   fi
done
  • Pro :C'est vraiment cool.
  • Inconvénient :Nécessite de lire le flux entier dans la mémoire. Ne convient pas aux flux infinis.

Version 2

Cette version résout le problème précédent des flux infinis. Mais vous perdrez la possibilité de répéter et de réorganiser les lignes.

Idem, les plages sont autorisées :

lines 1 1,4-6 9-

imprimera les lignes 1, 4, 5, 6, 9 et tout jusqu'à la fin. Si l'ensemble de lignes est borné, quitte dès que la dernière ligne est lue.

#!/bin/bash

lines=()
tillend=0
maxline=0

# Process arguments
IFS=', ' read -ra args <<< "[email protected]"

for arg in "${args[@]}"; do
   if [[ $arg = +([[:digit:]]) ]]; then
       arg=$arg-$arg
   fi
   if [[ $arg =~ ([[:digit:]]+)-([[:digit:]]*) ]]; then
      ((from=10#${BASH_REMATCH[1]}))
      ((from==0)) && from=1
      ((tillend && from>=tillend)) && continue
      if [[ -z ${BASH_REMATCH[2]} ]]; then
         tillend=$from
         continue
      fi
      ((to=10#${BASH_REMATCH[2]}))
      if ((from>to)); then
         printf >&2 "Invalid lines order: %s\n" "$arg"
         exit 1
      fi
      ((maxline<to)) && maxline=$to
      for ((i=from;i<=to;++i)); do
         lines[i]=1
      done
   else
      printf >&2 "Invalid argument \`%s'\n" "$arg"
      exit 1
   fi
done

# If nothing to read, exit
((tillend==0 && ${#lines[@]}==0)) && exit

# Now read stdin
linenb=0
while IFS= read -r line; do
   ((++linenb))
   ((tillend==0 && maxline && linenb>maxline)) && exit
   if [[ ${lines[linenb]} ]] || ((tillend && linenb>=tillend)); then
      printf '%s\n' "$line"
   fi
done
  • Pro :c'est vraiment cool et ne lit pas le flux complet en mémoire.
  • Inconvénient :Impossible de répéter ou de réorganiser les lignes comme dans la version 1. La vitesse n'est pas son point fort.

Autres réflexions

Si vous voulez vraiment un script général génial qui fasse ce que font la version 1 et la version 2, et plus encore, vous devriez certainement envisager d'utiliser un autre langage, par exemple Perl :vous gagnerez beaucoup (en particulier en vitesse) ! vous pourrez avoir de belles options qui feront beaucoup de choses beaucoup plus cool. Cela pourrait en valoir la peine à long terme, car vous voulez un script général et réutilisable. Vous pourriez même finir par avoir un script qui lit les e-mails !

Avis de non-responsabilité. Je n'ai pas bien vérifié ces scripts... alors méfiez-vous des bugs !


Linux
  1. Comment supprimer les lignes vides d'un fichier (y compris les tabulations et les espaces) ?

  2. Comment créer un fichier temporaire en script shell ?

  3. Comment Cater un fichier depuis Awk ?

  4. Comment ajouter plusieurs lignes à un fichier ?

  5. Awk de différentes lignes ?

Comment déplacer plusieurs types de fichiers simultanément à partir de la ligne de commande

Comment joindre plusieurs lignes en une seule dans un fichier sous Linux

Comment mélanger les lignes dans un fichier sous Linux

Comment inverser les lignes d'un fichier par caractère sous Linux

Comment supprimer des lignes d'un fichier à l'aide de la commande Sed

Comment supprimer les caractères (^M) d'un fichier sous Linux