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

j'aimerais avoir un script, appelé lines.sh que je peux tuyau de données pour sélectionner une série de lignes.

Par exemple, si j'avais le fichier suivant:

.txt

a 
b
c
d

alors je pourrais courir:

cat test.txt | lines 2,4

et il produirait

b
d

j'utilise zsh, mais préférerais une solution bash si possible.

7
demandé sur tinySandy 2014-12-16 19:58:30

7 réponses

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="" 'BEGIN{split(s, a, ","); for (i in a) b[a[i]]} NR in b' ""

puis donner les permissions d'exécution:

chmod +x lines.sh

et appelez-le comme:

./lines.sh '2,4' 'test.txt'
7
répondu anubhava 2014-12-16 17:54:23

Essayer sed :

sed -n '2p; 4p' inputFile

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

vous pouvez également utiliser des gammes, par exemple:

sed -n '2,4p' inputFile
3
répondu pfnuesel 2014-12-16 17:03:08

deux versions pures Bash. Puisque vous êtes à la recherche de solutions générales et réutilisables, vous pourriez aussi bien mettre un peu d'effort dans cela. (Voir aussi la dernière section).

Version 1

ce script slurps l'ensemble du stdin dans un tableau (en utilisant mapfile , donc il est assez efficace) et ensuite 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 virgule. Les lignes sont imprimées exactement dans l'ordre les arguments sont donnés:

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

va Imprimer 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.

Ici, vous allez:

#!/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.
  • Con: doit lire le flux entier dans la mémoire. Ne convient pas pour les flux infinis.

Version 2

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

même chose, les fourchettes sont permises:

lines 1 1,4-6 9-

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

#!/bin/bash

lines=()
tillend=0
maxline=0

# Process 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]}))
      ((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 de ne pas lire l'intégralité du flux dans la mémoire.
  • Con: ne peut pas répéter ou réorganiser les lignes comme la Version 1. La vitesse n'est pas le point le plus fort.

autres réflexions

si vous voulez vraiment un script général génial qui fait ce que la Version 1 et la Version 2 fait, et plus encore, vous devriez certainement envisager d'utiliser une autre langue, par exemple, Perl: vous gagnerez beaucoup (en particulier la vitesse)! vous serez en mesure d'avoir de belles des options qui feront beaucoup de choses plus cool. Cela vaut peut-être 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!


Disclaimer. Je n'ai pas vérifié ces scripts... méfiez-vous donc des bugs!

3
répondu gniourf_gniourf 2014-12-16 18:02:08

solution rapide pour votre ami. Entrée:

.txt

a
b
c
d
e
f
g
h
i
j

test.sh

lines (){
sed -n "$( echo "$@" | sed 's/[0-9]\+/&p;/g')"
}

cat 1.txt | lines 1 5 10

Ou si vous voulez avoir votre lines comme script:

lines.sh

IFS=',' read -a lines <<< ""; sed -n "$( echo "${lines[@]}" | sed 's/[0-9]\+/&p;/g')" ""

./lines.sh 1,5,10 test.txt

sortie dans les deux cas:

a
e
j
2
répondu tinySandy 2014-12-18 06:54:47

S'il s'agit d'une opération ponctuelle et qu'il n'y a pas beaucoup de lignes à choisir, vous pouvez utiliser pick pour les sélectionner manuellement:

cat test.txt | pick | ...

un écran interactif s'ouvrira vous permettant de sélectionner ce que vous voulez.

2
répondu Bernardo Rufino 2016-01-09 04:25:37

bien, à condition que:

  • votre fichier est assez petit
  • vous n'avez pas de point-virgule (ou un autre personnage de votre choix) dans le fichier
  • cela ne vous dérange pas d'utiliser plusieurs pipes

, vous pouvez utiliser quelque chose comme:

cat test.txt |tr "\n" ";"|cut -d';' -f2,4|tr ";" "\n"

où-f2, 4 indique les lignes que vous voulez extraire

1
répondu ChatterOne 2014-12-16 17:08:10

essayez ceci:

file=
for var in "$@"  //var is all line numbers
do
sed -n "${var}p" $file
done

j'ai créé un script avec 1 paramètre de fichier, et un nombre illimité de paramètres pour les numéros de ligne. Vous l'appelleriez ainsi:

lines txt 2 3 4...etc
1
répondu Bolboa 2014-12-16 18:29:58