Comment inverser l'ordre des lignes dans un fichier?

642

Je voudrais inverser l'ordre des lignes dans un fichier texte (ou stdin), en préservant le contenu de chaque ligne.

Donc, c'est-à-dire en commençant par:

foo
bar
baz

J'aimerais finir avec

baz
bar
foo

Existe-t-il un utilitaire de ligne de commande UNIX standard pour cela?

Scotty Allen
la source
2
Remarque importante sur l'inversion des lignes: assurez-vous d'abord que votre fichier comporte un retour à la ligne de fin . Sinon, les deux dernières lignes d'un fichier d'entrée seront fusionnées en une seule ligne dans un fichier de sortie (au moins en utilisant le perl -e 'print reverse <>'mais cela s'applique probablement aussi à d'autres méthodes).
jakub.g
doublon possible de Comment inverser les lignes d'un fichier texte?
Greg Hewgill
Également à peu près un doublon (bien que plus ancien) d' unix.stackexchange.com/questions/9356/… . Comme dans ce cas, la migration vers unix.stackexchange.com est probablement appropriée.
mc0e

Réponses:

445

Queue BSD:

tail -r myfile.txt

Référence: pages de manuel FreeBSD , NetBSD , OpenBSD et OS X.

Jason Cohen
la source
120
N'oubliez pas que l'option «-r» n'est pas compatible POSIX. Les solutions sed et awk ci-dessous vont fonctionner même dans les systèmes les plus gagnants.
armes
32
Je viens de l'essayer sur Ubuntu 12.04 et j'ai découvert qu'il n'y avait pas d'option -r pour ma version de tail (8.13). Utilisez plutôt «tac» (voir la réponse de Mihai ci-dessous).
odeur
12
La coche doit se déplacer en dessous vers tac. tail -r échoue sur Ubuntu 12/13, Fedora 20, Suse 11.
rickfoosusa
3
tail -r ~ / 1 ~ tail: option invalide - r Essayez `tail --help 'pour plus d'informations. ressembler à sa nouvelle option
Bohdan
6
La réponse devrait certainement mentionner qu'il s'agit uniquement de BSD, d'autant plus que l'OP a demandé un utilitaire "UNIX standard". Ce n'est pas dans GNU tail donc ce n'est même pas une norme de facto.
DanC
1403

A noter également: tac(le, ahem, revers de cat). Une partie de coreutils .

Retourner un fichier dans un autre

tac a.txt > b.txt
Mihai Limbășan
la source
72
Particulièrement utile de mentionner à ceux qui utilisent une version de queue sans option -r! (La plupart des utilisateurs de Linux ont GNU tail, qui n'a pas de -r, nous avons donc GNU tac).
oylenshpeegul
11
Juste une note, parce que les gens ont déjà mentionné tac, mais tac ne semble pas être installé sur OS X. Ce n'est pas difficile d'écrire un substitut en Perl, mais je n'ai pas le vrai.
Chris Lutz
5
Vous pouvez obtenir GNU tac pour OS X auprès de Fink. Vous voudrez peut-être également obtenir GNU tail, car il fait certaines choses que BSD ne fait pas.
oylenshpeegul, le
25
Si vous utilisez OS X avec homebrew, vous pouvez installer tac en utilisant brew install coreutils(installe gtacpar défaut).
Robert
3
L'un des problèmes est que si le fichier n'a pas de nouvelle ligne de fin, les 2 premières lignes peuvent être jointes en une seule ligne. echo -n "abc\ndee" > test; tac test.
CMCDragonkai
161

Il y a les astuces sed bien connues :

# reverse order of lines (emulates "tac")
# bug/feature in HHsed v1.5 causes blank lines to be deleted
sed '1!G;h;$!d'               # method 1
sed -n '1!G;h;$p'             # method 2

(Explication: ajouter une ligne non initiale pour conserver le tampon, permuter la ligne et conserver le tampon, imprimer la ligne à la fin)

Alternativement (avec une exécution plus rapide) à partir des uniformes awk :

awk '{a[i++]=$0} END {for (j=i-1; j>=0;) print a[j--] }' file*

Si vous ne vous en souvenez pas,

perl -e 'print reverse <>'

Sur un système avec des utilitaires GNU, les autres réponses sont plus simples, mais tout le monde n'est pas GNU / Linux ...

éphémère
la source
4
De la même source: awk '{a [i ++] = $ 0} END {for (j = i-1; j> = 0;) imprime un fichier [j--]}' * Les versions sed et awk fonctionnent sur mon routeur busybox. 'tac' et 'tail -r' ne le font pas.
armes
8
Je souhaite que celui-ci soit la réponse acceptée. coz sed est toujours disponible, mais pas tail -ret tac.
ryenus
@ryenus: tacdevrait gérer des fichiers arbitraires volumineux qui ne tiennent pas en mémoire (la longueur de ligne est cependant toujours limitée). Il n'est pas clair si la sedsolution fonctionne pour de tels fichiers.
jfs
Seul problème cependant: préparez-vous à attendre :-)
Antoine Lizée
1
Plus précisément: le code sed est en O (n ^ 2), et peut être TRÈS lent pour les gros fichiers. D'où mon vote positif pour l'alternative awk, linéaire. Je n'ai pas essayé l'option perl, moins compatible avec la tuyauterie.
Antoine Lizée
71

à la fin de votre commande mettez: | tac

tac fait exactement ce que vous demandez, il "Ecrit chaque FICHIER sur la sortie standard, dernière ligne en premier".

tac est l'opposé de cat :-).

Yakir GIladi Edry
la source
Pourquoi le ferait-il? Veuillez expliquer la valeur de la taccommande, ceci est utile pour les nouveaux utilisateurs qui pourraient finir par rechercher le même sujet.
Nic3500
11
Cela devrait vraiment être la réponse acceptée. Dommage que ce qui précède ait autant de votes.
joelittlejohn
62

S'il vous arrive d'être en cours d' vimutilisation

:g/^/m0
DerMike
la source
5
Connexe: Comment inverser l'ordre des lignes? à Vim SE
kenorb
4
Je voterais pour si vous expliquiez brièvement ce qu'il a fait.
mc0e
2
Oui, je comprends ce bit, mais je voulais dire décomposer ce que font les différents bits de la commande vim. J'ai maintenant regardé la réponse liée à @kenorb, qui fournit l'explication.
mc0e
5
g signifie "faire cela globalement. ^ signifie" le début d'une ligne ". m signifie" déplacer la ligne vers un nouveau numéro de ligne. 0 est la ligne vers laquelle se déplacer. 0 signifie "haut du fichier, avant la ligne actuelle 1". Donc: "Trouvez chaque ligne qui a un début et déplacez-la vers la ligne numéro 0." Vous trouvez la ligne 1 et la déplacez vers le haut. Ne fait rien. Recherchez ensuite la ligne 2 et déplacez-la au-dessus de la ligne 1, en haut du fichier. Trouvez maintenant la ligne 3 et déplacez -la vers le haut. Répétez cette opération pour chaque ligne. À la fin, vous terminez en déplaçant la dernière ligne vers le haut. Lorsque vous avez terminé, vous avez inversé toutes les lignes.
Ronopolis
Il convient de noter que la commande globale: g se comporte d'une manière très particulière par rapport à la simple utilisation de plages. Par exemple, la commande ":% m0" n'inversera pas l'ordre des lignes, tandis que ":% normal ddggP" le fera (tout comme ": g / ^ / normal ddggP"). Belle astuce et explication ... Oh oui, j'ai oublié le jeton "voir: aide: g pour plus d'informations" ...
Nathan Chappell
51
tac <file_name>

exemple:

$ cat file1.txt
1
2
3
4
5

$ tac file1.txt
5
4
3
2
1
jins
la source
42
$ (tac 2> /dev/null || tail -r)

Essayez tac, qui fonctionne sous Linux, et si cela ne fonctionne pas, utilisez-le tail -r, qui fonctionne sous BSD et OSX.

DigitalRoss
la source
4
Pourquoi pas tac myfile.txt- qu'est-ce qui me manque?
sauge
8
@sage, pour revenir au tail -rcas où tacn'est pas disponible. tacn'est pas conforme à POSIX. Ni l'un ni l'autre tail -r. Toujours pas infaillible, mais cela améliore les chances de fonctionnement.
slowpoison
Je vois - pour les cas où vous ne pouvez pas modifier manuellement / interactivement la commande en cas d'échec. Suffisant pour moi.
sauge
3
Vous avez besoin d'un test approprié pour voir si le tac est disponible. Que se passe-t-il si tacest disponible, mais manque de RAM et échange à mi-chemin en consommant un flux d'entrée géant. Il échoue, puis tail -rréussit à traiter le reste du flux, ce qui donne un résultat incorrect.
mc0e
@PetrPeller Voir la réponse ci-dessus commentaire de Robert pour OSX use homebrew. brew install coreutils et utilisez gtacà la place tacet si vous préférez, ajoutez tac comme alias gtacsi, par exemple, vous vouliez un script shell qui l'utilisait sur plusieurs plates-formes (Linux, OSX)
lacostenycoder
24

Essayez la commande suivante:

grep -n "" myfile.txt | sort -r -n | gawk -F : "{ print $2 }"
kenorb
la source
au lieu de la déclaration gawk, je ferais quelque chose comme ceci: sed 's/^[0-9]*://g'
bng44270
2
pourquoi ne pas utiliser "nl" au lieu de grep -n?
Good Person
3
@GoodPerson, nlpar défaut, ne numérotera pas les lignes vides. L' -baoption est disponible sur certains systèmes, elle n'est pas universelle (HP / UX me vient à l'esprit, même si je ne le souhaite pas) alors grep -nqu'elle numérotera toujours chaque ligne qui correspond à l'expression régulière (dans ce cas vide).
ghoti
1
Au lieu de gawk j'utilisecut -d: -f2-
Alexander Stumpf
17

Just Bash :) (4.0+)

function print_reversed {
    local lines i
    readarray -t lines

    for (( i = ${#lines[@]}; i--; )); do
        printf '%s\n' "${lines[i]}"
    done
}

print_reversed < file
konsolebox
la source
2
+1 pour la réponse en bash et pour O (n) et pour ne pas utiliser la récursivité (+3 si je le pouvais)
nhed
2
Essayez ceci avec un fichier contenant la ligne -neneneneneneneet voyez pourquoi les gens recommandent de toujours utiliser à la printf '%s\n'place de echo.
mtraceur
@mtraceur Je serais d'accord avec ça cette fois puisque c'est une fonction générale.
konsolebox
11

La méthode la plus simple consiste à utiliser la taccommande. tacest catl'inverse. Exemple:

$ cat order.txt
roger shah 
armin van buuren
fpga vhdl arduino c++ java gridgain
$ tac order.txt > inverted_file.txt
$ cat inverted_file.txt
fpga vhdl arduino c++ java gridgain
armin van buuren
roger shah 
Iekatandilburg
la source
1
Je ne sais pas pourquoi cette réponse apparaît avant celle ci-dessous, mais c'est une dupe de stackoverflow.com/a/742485/1174784 - qui a été publiée des années auparavant.
anarcat
10

J'aime vraiment la réponse " tail -r ", mais ma réponse gawk préférée est ....

gawk '{ L[n++] = $0 } 
  END { while(n--) 
        print L[n] }' file
Tim Menzies
la source
Testé avec mawksur Ubuntu 14.04 LTS - fonctionne, il n'est donc pas spécifique à GNU awk. +1
Sergiy Kolodyazhnyy
n++peut être remplacé parNR
karakfa
3

MODIFIER ce qui suit génère une liste de nombres triés au hasard de 1 à 10:

seq 1 10 | sort -R | tee /tmp/lst |cat <(cat /tmp/lst) <(echo '-------') **...**

où les points sont remplacés par une commande réelle qui inverse la liste

tac

seq 1 10 | sort -R | tee /tmp/lst |cat <(cat /tmp/lst) <(echo '-------') \
<(tac)

python: utilisation de [:: - 1] sur sys.stdin

seq 1 10 | sort -R | tee /tmp/lst |cat <(cat /tmp/lst) <(echo '-------') \
<(python -c "import sys; print(''.join(([line for line in sys.stdin])[::-1]))")
Yauhen Yakimovich
la source
3

Pour une solution multi-OS (c'est-à-dire OSX, Linux) qui peut utiliser à l' tacintérieur d'un script shell, utilisez l'homebrew comme d'autres l'ont mentionné ci-dessus, alors alias tac comme ceci:

Installer la lib

Pour MacOS

brew install coreutils

Pour Linux Debian

sudo apt-get update
sudo apt-get install coreutils 

Ajoutez ensuite un alias

echo "alias tac='gtac'" >> ~/.bash_aliases (or wherever you load aliases)
source ~/.bash_aliases
tac myfile.txt
lacostenycoder
la source
2

Cela fonctionnera à la fois sur BSD et GNU.

awk '{arr[i++]=$0} END {while (i>0) print arr[--i] }' filename
R. Kumar
la source
1

Si vous souhaitez modifier le fichier sur place, vous pouvez exécuter

sed -i '1!G;h;$!d' filename

Cela supprime la nécessité de créer un fichier temporaire, puis de supprimer ou renommer l'original et a le même résultat. Par exemple:

$tac file > file2
$sed -i '1!G;h;$!d' file
$diff file file2
$

Sur la base de la réponse de l'éphémient , qui a fait presque, mais pas tout à fait, ce que je voulais.

Mark Booth
la source
1

Il m'arrive que je souhaite obtenir efficacement les dernières nlignes d'un très gros fichier texte .

La première chose que j'ai essayée est tail -n 10000000 file.txt > ans.txt, mais je l'ai trouvée très lente, car tailelle doit chercher à l'emplacement puis recule pour imprimer les résultats.

Quand je me rends compte, je passe à une autre solution: tac file.txt | head -n 10000000 > ans.txt. Cette fois, la position de recherche a juste besoin de se déplacer de la fin à l'emplacement souhaité et cela économise 50% de temps !

Message à emporter:

À utiliser tac file.txt | head -n nsi votre tailn'a pas l' -roption.

youkaichao
la source
0

Meilleure solution:

tail -n20 file.txt | tac
ITNM
la source
Bienvenue dans Stack Overflow! Bien que cet extrait de code puisse résoudre la question, y compris une explication aide vraiment à améliorer la qualité de votre message. N'oubliez pas que vous répondrez à la question des lecteurs à l'avenir et que ces personnes ne connaissent peut-être pas les raisons de votre suggestion de code. Essayez également de ne pas surcharger votre code avec des commentaires explicatifs, cela réduit la lisibilité du code et des explications!
kayess
0

Pour les utilisateurs Emacs: C-x h(sélectionnez l'ensemble du fichier), puis M-x reverse-region. Fonctionne également pour sélectionner uniquement les pièces ou les lignes et les restaurer.

Marius Hofert
la source
0

Je vois beaucoup d'idées intéressantes. Mais essayez mon idée. Canalisez votre texte dans ceci:

rev | tr '\ n' '~' | rev | tr '~' '\ n'

ce qui suppose que le caractère '~' n'est pas dans le fichier. Cela devrait fonctionner sur chaque shell UNIX remontant à 1961. Ou quelque chose comme ça.

chauffeur
la source
-1

J'avais la même question, mais je voulais aussi que la première ligne (en-tête) reste au top. Je devais donc utiliser la puissance de awk

cat dax-weekly.csv | awk '1 { last = NR; line[last] = $0; } END { print line[1]; for (i = last; i > 1; i--) { print line[i]; } }'

PS fonctionne également dans cygwin ou gitbash

KIC
la source
Cela semble se traduire par 1\n20\n19...2\nplutôt que 20\n19...\2\n1\n.
Mark Booth
-1

Vous pouvez le faire avec vim stdinet stdout. Vous pouvez également utiliser expour être conforme à POSIX . vimest juste le mode visuel pour ex. En fait, vous pouvez utiliser exavec vim -eou vim -E( exmode amélioré ). vimest utile parce que contrairement à des outils comme sedcelui-ci, le fichier sedest mis en mémoire tampon pour l'édition, tandis que est utilisé pour les flux. Vous pourrez peut-être utiliser awk, mais vous devrez tout mettre manuellement en mémoire tampon dans une variable.

L'idée est de faire ce qui suit:

  1. Lire depuis stdin
  2. Pour chaque ligne, déplacez-le sur la ligne 1 (pour inverser). Le commandement est g/^/m0. Cela signifie globalement, pour chaque ligne g; correspond au début de la ligne, qui correspond à tout ^; déplacez-le après l'adresse 0, qui est la ligne 1m0 .
  3. Imprimez tout. Le commandement est %p. Cela signifie pour la gamme de toutes les lignes %; imprimer la ligne p.
  4. Quittez de force sans enregistrer le fichier. Le commandement est q!. Cela signifie quitter q; avec force !.
# Generate a newline delimited sequence of 1 to 10
$ seq 10
1
2
3
4
5
6
7
8
9
10

# Use - to read from stdin.
# vim has a delay and annoying 'Vim: Reading from stdin...' output
# if you use - to read from stdin. Use --not-a-term to hide output.
# --not-a-term requires vim 8.0.1308 (Nov 2017)
# Use -E for improved ex mode. -e would work here too since I'm not
# using any improved ex mode features.
# each of the commands I explained above are specified with a + sign
# and are run sequentially.
$ seq 10 | vim - --not-a-term -Es +'g/^/m0' +'%p' +'q!'
10
9
8
7
6
5
4
3
2
1
# non improved ex mode works here too, -e.
$ seq 10 | vim - --not-a-term -es +'g/^/m0' +'%p' +'q!'

# If you don't have --not-a-term, use /dev/stdin
seq 10 | vim -E +'g/^/m0' +'%p' +'q!' /dev/stdin

# POSIX compliant (maybe)
# POSIX compliant ex doesn't allow using + sign to specify commands.
# It also might not allow running multiple commands sequentially.
# The docs say "Implementations may support more than a single -c"
# If yours does support multiple -c
$ seq 10 | ex -c "execute -c 'g/^/m0' -c '%p' -c 'q!' /dev/stdin

# If not, you can chain them with the bar, |. This is same as shell
# piping. It's more like shell semi-colon, ;.
# The g command consumes the |, so you can use execute to prevent that.
# Not sure if execute and | is POSIX compliant.
seq 10 | ex -c "execute 'g/^/m0' | %p | q!" /dev/stdin

Comment rendre cela réutilisable

J'utilise un script que j'appelle ved(comme l'éditeur vim sed) pour utiliser vim pour éditer stdin. Ajoutez ceci à un fichier appelé veddans votre chemin:

#!/usr/bin/env sh

vim - --not-a-term -Es "$@" +'%p | q!'

J'utilise une +commande au lieu de +'%p' +'q!', car vim vous limite à 10 commandes. Leur fusion permet donc "$@"d'avoir 9+ commandes au lieu de 8.

Ensuite, vous pouvez faire:

seq 10 | ved +'g/^/m0'

Si vous n'avez pas vim 8, mettez-le à la vedplace:

#!/usr/bin/env sh

vim -E "$@" +'%p | q!' /dev/stdin
dosentmatter
la source
-3
rev
text here

ou

rev <file>

ou

rev texthere
user13575069
la source
Salut, bienvenue dans Stack Overflow! Lorsque vous répondez à une question, vous devez inclure une sorte d'explication, comme ce que l'auteur a fait de mal et ce que vous avez fait pour y remédier. Je vous le dis parce que votre réponse a été signalée comme de mauvaise qualité et est en cours de révision. Vous pouvez modifier votre réponse en cliquant sur le bouton "Modifier".
Federico Grandi
Esp. les nouvelles réponses à des questions anciennes et bien répondues nécessitent une justification suffisante pour ajouter une autre réponse.
Gert Arnold
rev inversera également le texte horizontalement, ce qui n'est pas le comportement souhaité.
D3l_Gato
-4

queue -r fonctionne dans la plupart des systèmes Linux et MacOS

seq 1 20 | queue -r

Bohdan
la source
-9
sort -r < filename

ou

rev < filename
Yoker Rekoy
la source
7
sort -rne fonctionne que si l'entrée est déjà triée, ce qui n'est pas le cas ici. revinverse les caractères par ligne mais garde l'ordre des lignes intact, ce qui n'est pas non plus ce que Scotty a demandé. Donc, cette réponse n'est en fait pas du tout une réponse.
Alexander Stumpf