Déplacer les N premières lignes de sortie à la fin sans utiliser de fichier temporaire


11

Imaginez une sortie d'une commande comme

44444
55555
11111
22222
33333

comment puis-je extraire les N premières lignes (les deux premières dans l'exemple ci-dessus) et les ajouter à la fin, mais sans utiliser le fichier temporaire (donc en utilisant uniquement des tuyaux)?

11111
22222
33333
44444
55555

Quelque chose dans le sens de | sed -n '3,5p;1,2p'(qui ne fonctionne évidemment pas car sed ne se soucie pas de l'ordre des commandes).


2
Pourquoi nous ne pouvons pas utiliser un fichier temporaire?
Braiam

Réponses:


13

Copiez simplement ces lignes dans le tampon de maintien (puis supprimez-les) et lorsque sur la dernière ligne, ajoutez le contenu du tampon de maintien à l'espace de motif:

some command | sed '1,NUMBER{           # in this range
H                                       # append line to hold space and
1h                                      # overwrite if it's the 1st line
d                                       # then delete the line
}
$G'                                     # on last line append hold buffer content

Avec gnu sedvous pourriez l'écrire comme

some command | sed '1,NUMBER{H;1h;d;};$G'

Voici une autre façon avec ol ' ed(il rlit la sortie de some commanddans le tampon de texte puis movule les lignes 1,NUMBERaprès la dernière $):

ed -s <<IN
r ! some command
1,NUMBERm$
,p
q
IN

Notez que - comme indiqué - ces deux échoueront si la sortie a moins de NUMBER+1 lignes. Une approche plus solide serait ( gnu sedsyntaxe):

some command | sed '1,NUMBER{H;1h;$!d;${g;q;};};$G'

celui-ci ne supprime que les lignes de cette plage tant qu'elles ne sont pas la dernière ligne ( $!d) - sinon, il écrase l'espace de motif avec le contenu du tampon de maintien ( g), puis se qferme (après avoir imprimé l'espace de motif actuel).


2
sed -e '1,NUMBER{H;1h;d;}' -e '$G'fonctionne également de manière portative (notez que certaines sedimplémentations ne peuvent pas contenir plus de quelques kilo-octets dans l'espace d'attente, donc NUMBER ne peut pas être trop grand là-bas)
Stéphane Chazelas

@ StéphaneChazelas - merci pour l'entrée - je choisis généralement une commande par ligne car je sais que c'est portable - la syntaxe des expressions multiples a toujours été un peu déroutante pour moi, par exemple, les docs posix disent "Le <right-brace> doit être précédé d'un <newline> " donc selon eux, cela ne devrait-il pas être sed -e '1,NUMBER{H;1h;d' -e'}' -e '$G'?
don_crissti

4
En règle générale, une nouvelle -eremplace une nouvelle ligne. d;}n'est pas encore POSIX mais portable. Cela sera corrigé dans la prochaine spécification POSIX. Voir austingroupbugs.net/view.php?id=944#c2720
Stéphane Chazelas

2
@don_crissti merci! ce serait cool si vous pouviez également inclure une courte explication de pourquoi cela fonctionne. (Bien sûr, je vais chercher, mais cela donnerait une réponse plus précieuse.)
Peter Uhnak

Dans ma tête, l'importable 1,NUMBER{H;1h;d;}n'est pas d'avoir un point-virgule immédiatement après l' accolade d' ouverture . Cela pourrait simplement être un bogue dans SunOS 4.1 seddont la solution de contournement est toujours câblée dans mes doigts 20 ans plus tard, cependant.
zwol

11

Une awkapproche:

cmd | awk -v n=3 '
  NR <= n {head = head $0 "\n"; next}
  {print}
  END {printf "%s", head}'

Un avantage par rapport à l' sedapproche de @ don_crissti est qu'il fonctionne toujours (sort les lignes) si la sortie a des nlignes ou moins.


Vous voudrez peut-être remplacer le code \nen dur par ORS, afin que cela fonctionne avec d'autres séparateurs d'enregistrement (par exemple, vous voulez utiliser des paragraphes, etc.).
fedorqui

6

J'ai xclipet avec cela cela peut être fait de cette façon:

./a_command | xclip -in && xclip -o | tail -n +3 && xclip -o | head -n 2

Voici sa description:

xclip - command line interface to X selections (clipboard)

NAME
       xclip - command line interface to X selections (clipboard)

SYNOPSIS
       xclip [OPTION] [FILE]...

DESCRIPTION
       Reads from standard in, or from one or more files, and makes the data available as an X selection for pasting into X applications. Prints current X selection to standard out.

       -i, -in
              read text into X selection from standard input or files (default)

       -o, -out
              prints the selection to standard out (generally for piping to a file or program)

3
+1 pour la (mauvaise) utilisation créative de xclip. La réponse a besoin d'un serveur X accessible / en cours d'exécution.
jofel

3

Une façon Perl:

perl -ne '$.<3?($f.=$_):print;}{print $f'

Ou, la même chose écrite moins cryptiquement:

perl -ne 'if($.<3){ $f.=$_ } else{ print } END{print $f}'

Par exemple:

$ cat file
44444
55555
11111
22222
33333

$ cat file | perl -ne '$.<3?($f.=$_):print;}{print $f'
11111
22222
33333
44444
55555

Explication

  • -ne: lire le fichier d'entrée / flux ligne par ligne et appliquer le script donné par -eà chaque ligne.
  • $.<3: $.est le numéro de ligne actuel, changez 3donc le nombre de lignes que vous souhaitez déplacer.
  • $.<3?($f.=$_):print;: c'est l'opérateur conditionnel, le format général est condition ? case1 : case2, il s'exécutera case1si conditionest vrai et case2s'il est faux. Ici, si le numéro de ligne actuel est inférieur à 3, il ajoute la ligne actuelle ( $_) à la variable $fet, si le numéro de ligne est supérieur à 3, il s'imprime.
  • }{ print $f: le }{raccourci est perl pour END{}. Il s'exécutera une fois que toutes les lignes d'entrée auront été traitées. À ce stade, nous aurons collecté toutes les lignes que nous voulons déplacer et nous aurons imprimé toutes celles que nous voulons laisser seules, alors imprimez les lignes enregistrées sous $f.

1
concernant votre version golfée, quelques caractères peuvent être supprimésperl -ne '$.<3?$f.=$_:print}{print $f
123

1

Utilisez POSIX ex. Oui, il est destiné à l'édition de fichiers, mais cela fonctionnera dans un pipeline.

printf %s\\n 111 222 333 444 555 | ex -sc '1,2m$|%p|q!' /dev/stdin

Cela peut avoir des commandes arbitraires ajoutées au début ou à la fin du pipeline et fonctionnera de la même manière. Mieux encore, étant donné la présence de /dev/stdin, il est compatible POSIX.

(Je ne sais pas si /dev/stdinest spécifié dans POSIX ou non, mais je vois qu'il est présent à la fois sous Linux et Mac OS X.)

Cela présente un avantage de lisibilité par rapport à l'utilisation sedde l'espace d'attente de - vous dites simplement ex«déplacez ces lignes jusqu'à la fin» et cela se fait. (Le reste des commandes signifie "imprimer le tampon" et "quitter", qui sont également assez lisibles.)

NB: La excommande donnée ci-dessus échouera si elle contient moins de 2 lignes en entrée.

Lectures complémentaires:


0

Un court pythonextrait:

#!/usr/bin/env python3
import sys
file_ = sys.argv[1]
lines = int(sys.argv[2])
with open(file_) as f:
    f = f.readlines()
    out = f[lines:] + f[:lines]
    print(''.join(out), end='')

Passez le nom de fichier comme premier argument et le nombre de lignes à déplacer comme deuxième argument.

Exemple:

$ cat input.txt
44444
55555
11111
22222
33333

$ ./sw_lines.py input.txt 2
11111
22222
33333
44444
55555

$ ./sw_lines.py input.txt 4
33333
44444
55555
11111
22222

0

Si vous pouvez stocker la sortie entière en mémoire:

data=$(some command)
n=42                  # or whatever
{ tail -n +$((n+1)) <<<"$data"; head -n $n <<<"$data"; } > outputfile

0

Voici une autre option qui nécessite GNU sed:

(x=$(gsed -u 3q);cat;printf %s\\n "$x")

-urend GNU sans sedtampon pour que la sedcommande ne consomme pas plus de 3 lignes de STDIN. La substitution de commande supprime les lignes vides, de sorte que la commande n'inclut pas les lignes vides à la fin de la sortie si les troisième, troisième et deuxième, ou troisième, deuxième et première lignes sont vides.

Vous pouvez également faire quelque chose comme ceci:

tee >(sponge /dev/stdout|sed -u 3q)|sed 1,3d

Sans sponge /dev/stdout|la commande échouerait avec de longues entrées. sponge /dev/stdoutpeut également être remplacé par tac|tac, même si cela s'imprime par exemple a\ncbsi l'entrée est a\nb\ncoù se \ntrouve un saut de ligne, ou avec (x=$(cat);printf %s\\n "$x"), même si cela supprime les lignes vides de la fin de l'entrée. La commande ci-dessus supprime la première ligne lorsque le nombre de lignes de l'entrée est un ou deux.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.