web-dev-qa-db-fra.com

Comment puis-je inverser l'ordre des lignes dans un fichier?

J'aimerais 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

Je voudrais finir avec 

baz
bar
foo

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

532
Scotty Allen

Queue BSD:

tail -r myfile.txt

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

383
Jason Cohen

Il convient également de mentionner: tac (the, ahem, reverse of cat). Partie de coreutils .

Basculer un fichier dans un autre

tac a.txt > b.txt
1217
Mihai Limbășan

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: la ligne suivante doit être précédée d'une réserve pour conserver la mémoire tampon, la ligne d'échange et la mémoire tampon conservées, la ligne d'impression à la fin)

Alternativement (avec une exécution plus rapide) des awk one-liners :

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 les utilitaires GNU, les autres réponses sont plus simples, mais tout le monde n’est pas GNU/Linux ...

141
ephemient

Si vous êtes en état d'utilisation vim

:g/^/m0
53
DerMike
$ (tac 2> /dev/null || tail -r)

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

39
DigitalRoss
tac <file_name>

exemple:

$ cat file1.txt
1
2
3
4
5

$ tac file1.txt
5
4
3
2
1
34
jins

Essayez la commande suivante:

grep -n "" myfile.txt | sort -r -n | gawk -F : "{ print $2 }"
23
DG

à la fin de votre commande, mettez: | tac

tac fait exactement ce que vous demandez, "écrivez chaque fichier sur la sortie standard, dernière ligne en premier."

tac est le contraire du chat :-).

17
Yakir GIladi Edry

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
15
konsolebox

La méthode la plus simple utilise la commande tac. tac est cat 's 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 
11
Yekatandilburg

J'aime beaucoup 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
10
Tim Menzies

EDIT 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 la commande réelle qui inverse la liste

tac

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

python: utiliser [:: - 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]))")
2
Yauhen Yakimovich

Pour les solutions multi-OS (OSX, Linux) pouvant utiliser tac dans un script Shell, utilisez homebrew comme d'autres l'ont mentionné ci-dessus, puis alias tac simplement comme ceci:

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

Cela fonctionnera à la fois sous BSD et GNU.

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

Meilleure solution:

tail -n20 file.txt | tac
2
ITNM

Si vous souhaitez modifier le fichier à la 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 de renommer le fichier d'origine et a le même résultat. Par exemple:

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

Basé sur le réponse par ephemient , qui a fait, mais pas tout à fait, ce que je voulais.

1
Mark Booth

tail -r fait le tour:

seq 1 20 | queue -r

0
Bohdan

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

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

qui suppose que le caractère '~' ne soit pas dans le fichier. Cela devrait fonctionner sur tous les shell UNIX remontant à 1961. Ou quelque chose comme ça.

0
driver

Pour les utilisateurs Emacs: C-x h (sélectionnez le fichier entier), puis M-x reverse-region. Fonctionne également pour ne sélectionner que les pièces ou les lignes et inverser celles-ci. 

0
Marius Hofert

J'avais la même question, mais je voulais aussi que la première ligne (en-tête) reste au premier plan. Donc j'avais besoin d'utiliser le pouvoir 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 en cygwin ou gitbash

0
KIC

Vous pouvez le faire avec vimstdin et stdout. Vous pouvez également utiliser ex pour être conforme à POSIX . vim est simplement le mode visuel pour ex. En fait, vous pouvez utiliser ex avec vim -e ou vim -E (mode ex amélioré) .vim est utile car contrairement aux outils comme sed, il tamponne le fichier pour le modifier, alors que sed est utilisé pour les flux. Vous pourrez peut-être utiliser awk, mais vous devrez mettre manuellement tout le tampon dans une variable.

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

  1. Lire de stdin
  2. Pour chaque ligne, déplacez-le vers la ligne 1 (pour inverser). La commande est g/^/m0. Cela signifie globalement, pour chaque ligne g; correspond au début de la ligne, ce qui correspond à tout ^; déplacez-le après l'adresse 0, qui correspond à la ligne 1 m0.
  3. Tout imprimer. La commande est %p. Cela signifie pour la plage de toutes les lignes %; affiche la ligne p.
  4. Quittez avec force sans enregistrer le fichier. La commande est q!. Cela signifie quit 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 ceci réutilisable

J'utilise un script que j'appelle ved (éditeur de vim comme sed) pour utiliser vim afin de modifier stdin. Ajoutez ceci à un fichier appelé ved dans 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. Donc, leur fusion permet au "$@" 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 ceci dans ved à la place:

#!/usr/bin/env sh

vim -E "$@" +'%p | q!' /dev/stdin
0
dosentmatter