Utilisation de la tête et de la queue pour saisir différents ensembles de lignes et enregistrer dans le même fichier

10

C'est donc pour les devoirs, mais je ne poserai pas la question spécifique des devoirs.

Je dois utiliser la tête et la queue pour saisir différents ensembles de lignes à partir d'un fichier. Donc, comme les lignes 6-11 et 19-24 et enregistrez-les dans un autre fichier. Je sais que je peux le faire en utilisant append tel que

head -11 file|tail -6 > file1; head -24 file| tail -6 >> file1. 

Mais je ne pense pas que nous soyons censés le faire.
Existe-t-il un moyen spécifique de combiner les commandes head et tail, puis de les enregistrer dans le fichier?

user2709291
la source
1
Vous demandent-ils spécifiquement d'utiliser headet tail? Si c'est le cas, votre solution est à peu près la meilleure que vous puissiez faire. Si vous êtes autorisé à utiliser d'autres programmes, sedou awksi vous autorisez de meilleures solutions (c'est-à-dire avec moins d'appels de processus).
2015
Oui, ils nous demandent d'utiliser la tête et la queue. Merci pour votre réponse.
user2709291
Une chose que je peux ajouter: Vous pouvez contourner la redirection de sortie annexant ( >>) en joignant les deux commandes entre parenthèses pour rediriger leur sortie concaténés: (head -11 file | tail -6; head -24 file | tail -6) > file1. Cela se résume vraiment à la préférence personnelle qui est plus agréable.
2015
Merci qui fonctionnera très bien. J'apprécie vraiment cela.
user2709291

Réponses:

11

Vous pouvez le faire avec headl'arithmétique seule et de base, si vous regroupez des commandes en { ... ; }utilisant une construction comme

{ head -n ...; head -n ...; ...; } < input_file > output_file

où toutes les commandes partagent la même entrée (merci @mikeserv ).
Obtenir les lignes 6-11 et 19-24 équivaut à:

head -n 5 >/dev/null  # dump the first 5 lines to `/dev/null` then
head -n 6             # print the next 6 lines (i.e. from 6 to 11) then
head -n 7 >/dev/null  # dump the next 7 lines to `/dev/null` ( from 12 to 18)
head -n 6             # then print the next 6 lines (19 up to 24)

Donc, en gros, vous exécuteriez:

{ head -n 5 >/dev/null; head -n 6; head -n 7 >/dev/null; head -n 6; } < input_file > output_file
don_crissti
la source
6

Vous pouvez utiliser la { … }construction de regroupement pour appliquer l'opérateur de redirection à une commande composée.

{ head -n 11 file | tail -n 6; head -n 24 file | tail -n 6; } >file1

Au lieu de dupliquer les premières lignes M + N et de ne conserver que le dernier N, vous pouvez ignorer les premières lignes M et dupliquer le N suivant. C'est beaucoup plus rapide sur les gros fichiers . Attention, l' +Nargument de tailn'est pas le nombre de lignes à sauter, mais un plus cela - c'est le numéro de la première ligne à imprimer avec des lignes numérotées à partir de 1.

{ tail -n +6 file | head -n 6; tail -n +19 file | head -n 6; } >file1

Dans les deux cas, le fichier de sortie n'est ouvert qu'une seule fois, mais le fichier d'entrée est parcouru une fois pour chaque extrait à extraire. Que diriez-vous de regrouper les entrées?

{ tail -n +6 | head -n 6; tail -n +14 | head -n 6; } <file >file1

En général, cela ne fonctionne pas. (Cela peut fonctionner sur certains systèmes, au moins lorsque l'entrée est un fichier normal.) Pourquoi? En raison de la mise en mémoire tampon d'entrée . La plupart des programmes, notamment tail, ne lisent pas leur octet d'entrée octet, mais quelques kilo-octets à la fois, car c'est plus rapide. tailLit donc quelques kilo-octets, saute un peu au début, passe un peu plus à headet s'arrête - mais ce qui est lu est lu et n'est pas disponible pour la commande suivante.

Une autre approche consiste à utiliser des headtuyaux /dev/nullpour sauter des lignes.

{ head -n 5 >/dev/null; head -n 6; head -n 7 >/dev/null; head -n 6; } <file >file1

Encore une fois, ce n'est pas garanti de fonctionner, en raison de la mise en mémoire tampon. Il se trouve que cela fonctionne avec la headcommande de GNU coreutils (celle que l'on trouve sur les systèmes Linux non intégrés), lorsque l'entrée provient d'un fichier normal. En effet, une fois que cette implémentation de heada lu ce qu'elle veut, elle définit la position du fichier sur le premier octet qu'elle n'a pas sorti. Cela ne fonctionne pas si l'entrée est un tuyau.

Un moyen plus simple d'imprimer plusieurs séquences de lignes à partir d'un fichier est d'appeler un outil plus généraliste tel que sed ou awk . (Cela peut être plus lent, mais cela n'a d'importance que pour les fichiers extrêmement volumineux.)

sed -n -e '6,11p' -e '19,24p' <file >file1
sed -e '1,5d' -e '12,18d' -e '24q' <file >file1
awk '6<=NR && NR<=11 || 19<=NR && NR<=24' <file >file1
awk 'NR==6, NR==11; NR==19, NR==24' <file >file1
Gilles 'SO- arrête d'être méchant'
la source
2
Cela ne fonctionne pas , c'est un comportement standard et spécifié - bien que, comme vous le dites, un tuyau ne soit pas une source d'entrée fiable pour une entrée partagée. DÉFAUTS DE DESCRIPTION DE L'UTILITÉ : Lorsqu'un utilitaire standard lit un fichier d'entrée recherché et se termine sans erreur avant qu'il n'atteigne la fin du fichier, l'utilitaire s'assurera que le décalage de fichier dans la description du fichier ouvert est correctement positionné juste après le dernier octet traité par L'utilité.
mikeserv
2

Je sais que vous avez dit que vous devez utiliser la tête et la queue, mais sed est certainement l'outil le plus simple pour le travail ici.

$ cat foo
a 1 1
a 2 1
b 1 1
a 3 1
c 3 1
c 3 1
$ sed -ne '2,4p;6p' foo
a 2 1
b 1 1
a 3 1
c 3 1

Vous pouvez même créer les blocs dans une chaîne avec un autre processus et l'exécuter via sed.

$ a="2,4p;6p"
$ sed -ne $a foo
a 2 1
b 1 1
a 3 1
c 3 1

-n annule la sortie, puis vous spécifiez des plages à imprimer avec p, avec le premier et le dernier numéro de la plage séparés par une virgule.

Cela étant dit, vous pouvez soit effectuer le regroupement de commandes suggéré par @don_crissti, soit parcourir le fichier plusieurs fois avec head / tail saisissant un morceau de lignes à chaque fois que vous passez.

$ head -4 foo | tail -3; head -6 foo | tail -1
a 2 1
b 1 1
a 3 1
c 3 1

Plus il y a de lignes dans un fichier et plus vous avez de blocs, plus sed sera efficace.

Falsenames
la source
2

Avec sedvous pourriez faire:

sed '24q;1,5d;12,18d' <infile >outfile

... Peut-être une solution plus efficace pourrait être trouvée avec head. Don a déjà montré comment cela pouvait très bien fonctionner, mais j'ai aussi joué avec. Quelque chose que vous pourriez faire pour gérer ce cas spécifique:

for   n in 5 6 7 6
do    head -n"$n" >&"$((1+n%2))"
done  <infile >outfile 2>/dev/null

... qui appellerait head4 fois l'écriture vers outfileou vers /dev/nullselon que la valeur de cette itération $nest un nombre pair ou impair.

Pour des cas plus généraux, j'ai bricolé cela à partir d'autres choses que j'avais déjà:

somehead()( 
### call it like:
### somehead -[repeat] [-][numlines]* <infile >outfile
    set -e -- "${1#-}" "$@"                             #-e for arg validation
    r=; cd -- "${TMP:-/tmp}"                            #go to tmp
    dd bs=4096 of="$$$$" <&4 2>&3 &                     #dd <in >tmpfile &bg
    until [ -s "$$$$" ]; do :; done                     #wait while tmpfile empty
    exec <"$$$$" 4<&-;   rm "$$$$"                      #<tmpfile; rm tmpfile
    [ "$3${1}0" -ne "$3${2#?}0" ]          ||           #validate args - chk $1
            shift "$(((r=-${1:--1})||1))"; shift        #shift 1||2
    while [ "$(((r+=(_n=1))-1))" -ne 0 ]   &&           #while ! $rptmax &&
          IFS= read -r l                   &&           #      ! EOF     &&
          printf "%.$(($1>0?${#l}+1:0))s" "$l           #      ? printf  do
";  do    for n do [ "${n#-}" -gt 0 ]      || exit      #args all -[nums>0]
          head "-n$((${n#-}-_n))" >&"$((n>(_n=0)?1:3))" #head -n?$1 >?[+-]
    done; done                                          #done and done
)   4<&0 3>/dev/null                                    #4<for dd 3>for head

Cela peut faire votre chose comme:

 seq 100 | somehead -1 -5 6 -7 6

... qui imprime ...

6
7
8
9
10
11
19
20
21
22
23
24

Il s'attend à ce que son premier argument soit un nombre de répétitions préfixé par un -, ou, à défaut, juste un -. Si un décompte est fourni, il répétera le modèle de ligne donné dans les arguments suivants autant de fois que spécifié et s'arrêtera dès qu'il l'aura fait.

Pour chaque argument qui suit, il interprétera un entier négatif pour indiquer un compte de lignes qui doit être écrit /dev/nullet un entier positif pour indiquer un compte de lignes qui doit être écrit stdout.

Ainsi, dans l'exemple ci-dessus, il imprime les 5 premières lignes /dev/null, les 6 suivantes stdout, les 7 suivantes /dev/nullet les 6 suivantes à nouveau stdout. Après avoir atteint le dernier de ses arguments et entièrement parcouru le -1nombre de répétitions, il se ferme ensuite. Si le premier argument avait été, -2il aurait répété le processus une fois de plus, ou si -aussi longtemps qu'il le pouvait.

Pour chaque cycle d'arg, la whileboucle est traitée une fois. En haut de chaque boucle, la première ligne de stdinest lue dans la variable shell $l. Ceci est nécessaire car while head </dev/null; do :; donese répétera indéfiniment - headindique dans son retour quand il a atteint la fin du fichier. Ainsi, la vérification par rapport à EOF est dédiée readet printfn'écrira $lplus une nouvelle ligne stdoutque si le deuxième argument est un entier positif.

La readvérification complique un peu la boucle car immédiatement après l'appel d'une autre boucle - une forboucle qui itère sur les arguments 2-$#comme représenté dans $nchaque itération de sa whileboucle parent . Cela signifie que pour chaque itération, le premier argument doit être décrémenté de un de la valeur spécifiée sur la ligne de commande, mais tous les autres doivent conserver leurs valeurs d'origine, et donc la valeur du $_nmarqueur var est soustraite de chacun, mais ne contient que valeur supérieure à 0 pour le premier argument.

Cela constitue la boucle principale de la fonction, mais la majeure partie du code est en haut et est destinée à permettre à la fonction de tamponner proprement même un tuyau en entrée. Cela fonctionne en appelant d'abord un arrière-plan ddpour copier son entrée dans un fichier tmp en sortie à des blocs de 4k par morceau. La fonction établit ensuite une boucle d'attente - qui ne devrait presque jamais terminer même un seul cycle complet - juste pour s'assurer qu'elle dda effectué au moins une seule écriture dans le fichier avant que la fonction ne remplace ensuite son stdin par un descripteur de fichier lié au fichier tmp et dissocie ensuite immédiatement le fichier avecrm. Cela permet à la fonction de traiter le flux de manière fiable sans nécessiter d'interruptions ou autrement pour le nettoyage - dès que la fonction libère sa revendication sur le fd, le fichier tmp cessera d'exister car son seul lien de système de fichiers nommé a déjà été supprimé.

mikeserv
la source
0

Utilisez une fonction bash comme celle-ci:

seq 1 30 > input.txt
f(){ head $1 input.txt | tail $2 >> output.txt ;}; f -11 -2; f -24 -3
cat output.txt
10
11
22
23
24

C'est un peu exagéré dans ce cas, mais si vos filtres grossissent, cela peut devenir une aubaine.

mkalkov
la source