Como posso reverter a ordem das linhas em um arquivo?

641

Gostaria de reverter a ordem das linhas em um arquivo de texto (ou stdin), preservando o conteúdo de cada linha.

Então, ou seja, começando com:

foo
bar
baz

Eu gostaria de terminar com

baz
bar
foo

Existe um utilitário de linha de comando UNIX padrão para isso?

Scotty Allen
fonte
2
Nota importante sobre a reversão das linhas: verifique se o seu arquivo tem uma nova linha à direita primeiro. Caso contrário, as duas últimas linhas de um arquivo de entrada serão mescladas em uma linha em um arquivo de saída (pelo menos usando o, perl -e 'print reverse <>'mas provavelmente também se aplica a outros métodos).
jakub.g
possível duplicata de Como reverter linhas de um arquivo de texto?
Greg Hewgill 28/09/2015
Também praticamente uma duplicata (embora mais antiga) do unix.stackexchange.com/questions/9356/… . Como nesse caso, a migração para unix.stackexchange.com é provavelmente apropriada.
Mc0e

Respostas:

444

Cauda BSD:

tail -r myfile.txt

Referência: FreeBSD páginas de manual do , NetBSD , OpenBSD e OS X.

Jason Cohen
fonte
120
Lembre-se de que a opção '-r' não é compatível com POSIX. As soluções sed e awk abaixo funcionarão mesmo nos sistemas mais exigentes.
armas
32
Tentei isso no Ubuntu 12.04 e descobri que não há opção -r para a minha versão do tail (8.13). Use 'tac' (veja a resposta de Mihai abaixo).
odigity 21/09/12
12
A marca de seleção deve passar abaixo para tac. cauda -r falhar no Ubuntu 12/13, Fedora 20, Suse 11.
rickfoosusa
3
tail -r ~ / 1 ~ tail: opção inválida - r Tente `tail --help 'para obter mais informações. parecido com sua nova opção
Bohdan 5/05
6
A resposta certamente deve mencionar que isso é apenas para BSD, principalmente porque o OP solicitou um utilitário "padrão UNIX". Isso não está na cauda do GNU, portanto, nem é um padrão de fato.
DanC
1402

Também vale a pena mencionar: tac(o, ahem, reverso de cat). Parte dos coreutils .

Virando um arquivo para outro

tac a.txt > b.txt
Mihai Limbășan
fonte
72
Vale a pena mencionar especialmente para aqueles que usam uma versão do tail com a opção no -r! (A maioria das pessoas com Linux tem GNU tail, que não possui -r, então temos o GNU tac).
oylenshpeegul
11
Apenas uma observação, porque as pessoas mencionaram tac antes, mas o tac não parece estar instalado no OS X. Não que seja difícil escrever um substituto no Perl, mas eu não tenho o verdadeiro.
31720 Chris Lutz
5
Você pode obter o GNU tac para OS X no Fink. Você também pode obter o GNU tail, pois ele faz algumas coisas que o BSD tail não faz.
oylenshpeegul
25
Se você usa o OS X com homebrew, pode instalar o tac usando brew install coreutils(instala como gtacpadrão).
25413 Robert
3
Um dos problemas é que, se o arquivo não tiver uma nova linha à direita, as 2 primeiras linhas poderão ser unidas como 1 linha. echo -n "abc\ndee" > test; tac test.
precisa saber é o seguinte
161

Existem os conhecidos truques sed :

# 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

(Explicação: acrescente uma linha não inicial para reter o buffer, troque a linha e retenha o buffer, imprima a linha no final)

Como alternativa (com execução mais rápida) dos awk one-liners :

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

Se você não se lembra disso,

perl -e 'print reverse <>'

Em um sistema com utilitários GNU, as outras respostas são mais simples, mas nem todo mundo é GNU / Linux ...

efémero
fonte
4
Da mesma fonte: awk '{a [i ++] = $ 0} END {for (j = i-1; j> = 0;) imprime um arquivo [j--]}' * As versões sed e awk funcionam em meu roteador busybox. 'tac' e 'tail -r' não.
armas
8
Eu desejo que esta seja a resposta aceita. coz sed está sempre disponível, mas não tail -re tac.
Ryenus
@ryenus: tacespera-se que lide com arquivos grandes arbitrários que não cabem na memória (o comprimento da linha ainda é limitado). Não está claro se a sedsolução funciona para esses arquivos.
jfs
O único problema, porém: estar preparado para esperar :-)
Antoine Lizée
1
Mais precisamente: o código sed está em O (n ^ 2) e pode ser MUITO lento para arquivos grandes. Daí o meu voto pela alternativa awk, linear. Eu não tentei a opção perl, menos favorável à tubulação.
Antoine Lizée
71

no final do seu comando, coloque: | tac

O tac faz exatamente o que você está pedindo: "Escreva cada arquivo na saída padrão, a última linha primeiro."

tac é o oposto de cat :-).

Yakir GIladi Edry
fonte
Por que ele deveria? Por favor, explique o valor do taccomando, isso é útil para novos usuários que podem acabar pesquisando o mesmo assunto.
precisa saber é o seguinte
11
Esta realmente deve ser a resposta aceita. Vergonha o acima exposto tem tantos votos.
Joelittlejohn
62

Se você estiver em vimuso

:g/^/m0
DerMike
fonte
5
Relacionado: Como reverter a ordem das linhas? em Vim SE
kenorb
4
Eu votaria nisso se você explicasse brevemente o que fez.
Mc0e 8/08
2
Sim, eu entendi isso, mas eu quis dizer detalhando o que os vários bits do comando vim estão fazendo. Agora olhei para a resposta @kenorb linked, que fornece a explicação.
Mc0e
5
g significa "faça isso globalmente. ^ significa" o início de uma linha ". m significa" mova a linha para um novo número de linha. 0 é para qual linha mover. 0 significa "parte superior do arquivo, antes da linha 1 atual". Portanto: "Encontre todas as linhas que tenham um começo e mova-as para a linha número 0." Você encontra a linha 1 e a move para o topo. Faz nada. Em seguida, encontre a linha 2 e mova-a acima da linha 1, para a parte superior do arquivo. Agora encontre a linha 3 e mova -a para o topo. Repita isso para todas as linhas. No final, você termina movendo a última linha para o topo. Quando terminar, você inverteu todas as linhas.
Ronopolis
Deve-se notar que o comando global: g se comporta de uma maneira muito particular vs simplesmente usando intervalos. Por exemplo, o comando ":% m0" não reverterá a ordem das linhas, enquanto ":% normal ddggP" o fará (como será ": g / ^ / ddggP normal"). Truque legal e explicação ... Oh sim, esqueci o token "ver: ajuda: g para mais informações" ...
Nathan Chappell
51
tac <file_name>

exemplo:

$ cat file1.txt
1
2
3
4
5

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

Tente tac, que funciona no Linux, e se isso não funcionar tail -r, use , que funciona no BSD e OSX.

DigitalRoss
fonte
4
Por que não tac myfile.txt- o que estou perdendo?
sage
8
@sage, para voltar ao tail -rcaso tacnão estiver disponível. tacnão é compatível com POSIX. Nem é tail -r. Ainda não é infalível, mas isso aumenta as chances de as coisas funcionarem.
slowpoison
Entendo - nos casos em que você não é capaz de alterar manualmente / interativamente o comando quando ele falha. Bom o suficiente para mim.
sage
3
Você precisa de um teste adequado para verificar se o tac está disponível. O que acontece se tacestiver disponível, mas ficar sem RAM e trocar no meio do consumo de um fluxo de entrada gigante. Ele falha e, em seguida, tail -rconsegue processar o restante do fluxo, fornecendo um resultado incorreto.
Mc0e
@PetrPeller Veja a resposta acima do comentário de Robert para OSX use homebrew. brew install coreutils e uso gtacno lugar do tace se o seu preferem add tac como um alias para gtacse por exemplo você queria um shell script que usou várias plataformas (Linux, OSX)
lacostenycoder
24

Tente o seguinte comando:

grep -n "" myfile.txt | sort -r -n | gawk -F : "{ print $2 }"
kenorb
fonte
em vez da declaração gawk, eu faria algo parecido com isto: sed 's/^[0-9]*://g'
bng44270 21/11/11
2
por que não usar "nl" em vez de grep -n?
Boa Pessoa
3
@ GoodPerson, nlpor padrão, não conseguirá numerar as linhas vazias. A -baopção está disponível em alguns sistemas, não é universal (o HP / UX vem à mente, mas eu gostaria que não fosse), enquanto grep -nsempre numerará todas as linhas que correspondem ao regex (neste caso vazio).
ghoti
1
Em vez de usar gawk Icut -d: -f2-
Alexander Stumpf
17

Just Bash :) (4.0 ou superior)

function print_reversed {
    local lines i
    readarray -t lines

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

print_reversed < file
konsolebox
fonte
2
+1 para resposta no bash e para O (n) e por não usar recursão (+3 se eu pudesse)
encerrou 8/14
2
Tente fazer isso com um arquivo que contém a linha -nenenenenenenee testemunhe o motivo pelo qual as pessoas recomendam sempre o uso em printf '%s\n'vez de echo.
Mtraceur
@traceur Concordo com isso desta vez, uma vez que esta é uma função geral.
precisa saber é o seguinte
11

O método mais simples é usar o taccomando tacé catinverso. Exemplo:

$ 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 
Yekatandilburg
fonte
1
não sei por que essa resposta aparece antes da resposta abaixo, mas é um truque do stackoverflow.com/a/742485/1174784 - que foi publicado anos antes.
anarcat
10

Eu realmente gosto da resposta " tail -r ", mas minha resposta favorita é ...

gawk '{ L[n++] = $0 } 
  END { while(n--) 
        print L[n] }' file
Tim Menzies
fonte
Testado com o mawkUbuntu 14.04 LTS - funciona, por isso não é específico ao GNU awk. 1
Sergiy Kolodyazhnyy
n++pode ser substituído porNR
karakfa
3

EDITAR o seguinte gera uma lista aleatória de números de 1 a 10:

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

onde os pontos são substituídos pelo comando real que inverte a lista

tac

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

python: usando [:: - 1] no 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
fonte
3

Para uma solução OS cruzada (OSX, Linux) que pode usar tac dentro de um script de shell, use homebrew como os outros mencionaram acima;

Instalar lib

Para MacOS

brew install coreutils

Para linux debian

sudo apt-get update
sudo apt-get install coreutils 

Em seguida, adicione o alias

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

Isso funcionará no BSD e no GNU.

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

Se você deseja modificar o arquivo no local, pode executar

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

Isso elimina a necessidade de criar um arquivo temporário e excluir ou renomear o original e tem o mesmo resultado. Por exemplo:

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

Baseado na resposta de ephemient , que fez quase, mas não exatamente, o que eu queria.

Mark Booth
fonte
1

Acontece que eu quero obter as últimas nlinhas de um arquivo de texto muito grande com eficiência .

A primeira coisa que tentei é tail -n 10000000 file.txt > ans.txt, mas achei muito lento, pois tailtem que procurar o local e depois voltar para imprimir os resultados.

Quando eu perceber, eu mudar para outra solução: tac file.txt | head -n 10000000 > ans.txt. Desta vez, a posição de busca só precisa se mover do final para o local desejado e economiza 50% de tempo !

Leve para casa a mensagem:

Use tac file.txt | head -n nse o seu tailnão tiver a -ropção.

youkaichao
fonte
0

Melhor solução:

tail -n20 file.txt | tac
ITNM
fonte
Bem-vindo ao Stack Overflow! Embora esse trecho de código possa resolver a questão, incluir uma explicação realmente ajuda a melhorar a qualidade da sua postagem. Lembre-se de que você está respondendo à pergunta dos leitores no futuro e essas pessoas podem não saber os motivos da sua sugestão de código. Tente também não sobrecarregar seu código com comentários explicativos, pois isso reduz a legibilidade do código e das explicações!
Kayess
0

Para usuários do Emacs: C-x h(selecione o arquivo inteiro) e depois M-x reverse-region. Também funciona apenas para selecionar partes ou linhas e revertê-las.

Marius Hofert
fonte
0

Eu vejo muitas idéias interessantes. Mas tente minha ideia. Canalize seu texto para isso:

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

que assume que o caractere '~' não está no arquivo. Isso deve funcionar em todos os shell UNIX desde 1961. Ou algo assim.

motorista
fonte
-1

Eu tinha a mesma pergunta, mas também queria que a primeira linha (cabeçalho) permanecesse no topo. Então eu precisava usar o poder do 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 também funciona em cygwin ou gitbash

KIC
fonte
Isso parece resultar em 1\n20\n19...2\nvez de 20\n19...\2\n1\n.
Mark Booth
-1

Você pode fazer isso com vim stdine stdout. Você também pode usar expara ser compatível com POSIX . vimé apenas o modo visual para ex. De fato, você pode usar excom vim -eou vim -E( exmodo aprimorado ). vimé útil porque, ao contrário de ferramentas como sedessa, armazena em buffer o arquivo para edição, enquanto sedé usado para fluxos. Você pode usar awk, mas terá que armazenar manualmente manualmente tudo em uma variável.

A ideia é fazer o seguinte:

  1. Leia de stdin
  2. Para cada linha, mova-o para a linha 1 (para reverter). Comando é g/^/m0. Isso significa globalmente, para cada linha g; coincidir com o início da linha, que corresponde a qualquer coisa ^; mova-o após o endereço 0, que é a linha 1m0 .
  3. Imprima tudo. Comando é %p. Isso significa para o intervalo de todas as linhas %; imprima a linha p.
  4. Saia vigorosamente sem salvar o arquivo. Comando é q!. Isso significa sair q; com força !.
# 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

Como tornar isso reutilizável

Eu uso um script que chamo ved(como o editor vim sed) para usar o vim para editar stdin. Adicione isso a um arquivo chamado vedno seu caminho:

#!/usr/bin/env sh

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

Estou usando um +comando em vez de +'%p' +'q!', porque o vim limita você a 10 comandos. Então, mesclá-los permite "$@"que você tenha 9+ comandos em vez de 8.

Então você pode fazer:

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

Se você não possui o vim 8, insira isso ved:

#!/usr/bin/env sh

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

ou

rev <file>

ou

rev texthere
user13575069
fonte
Olá, bem-vindo ao Stack Overflow! Ao responder a uma pergunta, você deve incluir algum tipo de explicação, como o que o autor fez de errado e o que você fez para corrigi-lo. Estou lhe dizendo isso porque sua resposta foi sinalizada como de baixa qualidade e está sendo revisada no momento. Você pode editar sua resposta clicando no botão "Editar".
Federico Grandi
Esp. novas respostas para perguntas antigas e bem respondidas precisam de ampla justificativa para adicionar mais uma resposta.
Gert Arnold
rev também inverte o texto horizontalmente, o que não é o comportamento desejado.
D3l_Gato
-4

tail -r funciona na maioria dos sistemas Linux e MacOS

seq 1 20 | cauda -r

Bohdan
fonte
-9
sort -r < filename

ou

rev < filename
Yoker Rekoy
fonte
7
sort -rsó funciona se a entrada já estiver classificada, o que não é o caso aqui. revinverte os caracteres por linha, mas mantém a ordem das linhas intacta, o que também não é o que Scotty pediu. Portanto, esta resposta é realmente nenhuma resposta.
Alexander Stumpf