Como obter comportamento inverso para `tail` e` head`?

55

Existe uma maneira de head/ tailum documento e obter a saída reversa; porque você não sabe quantas linhas existem em um documento?

Ou seja, eu só quero obter tudo, menos as 2 primeiras linhas de foo.txtanexar a outro documento.

chrisjlee
fonte

Respostas:

57

Você pode usar isso para remover as duas primeiras linhas:

tail -n +3 foo.txt

e isso para remover as duas últimas linhas:

head -n -2 foo.txt

(supondo que o arquivo termine com \npara o último)


Assim como para o uso padrão taile headessas operações não são destrutivas. Use >out.txtse você deseja redirecionar a saída para algum novo arquivo:

tail -n +3 foo.txt >out.txt

Caso out.txtjá exista, ele substituirá esse arquivo. Use em >>out.txtvez de >out.txtse preferir que a saída seja anexada out.txt.

Stéphane Gimenez
fonte
3
re "head, quando o arquivo termina com \n" .. Ele funciona para todos os números inteiros negativos, exceto os -n -0que não retornam nada , da mesma maneira que -n 0faria (usando: head (GNU coreutils) 7.4) ... No entanto, quando um trailing \nestá presente, -n -0é impresso como se poderia esperar do -, ie. ele imprime o arquivo inteiro ... Então, ela não funciona para todos os valores negativos não-zero .. mas -0falha quando não há nenhuma fuga\n
Peter.O
@ Fred: Estranho mesmo ... (o mesmo com 8.12 aqui).
Stéphane Gimenez
Esta operação é destrutiva? Como eu quero copiar o inverso das duas primeiras linhas do documento para outro?
Chrisjlee
@ Chris: Não, eles apenas imprimem o resultado em sua "saída padrão", que geralmente é conectada ao terminal. Eu adicionei alguns detalhes sobre como redirecionar a saída para alguns arquivos.
Stéphane Gimenez
7
head -n -2não é compatível com POSIX .
L0b0
9

Se você deseja todas, exceto as primeiras linhas N-1, ligue tailcom o número de linhas +N. (O número é o número da primeira linha que você deseja reter, iniciando em 1, ou seja, +1 significa iniciar no topo, +2 significa ignorar uma linha e assim por diante).

tail -n +3 foo.txt >>other-document

Não existe uma maneira fácil e portátil de pular as últimas N linhas. O GNU headpermite head -n +Ncomo contrapartida de tail -n +N. Caso contrário, se você tiver tac(por exemplo, GNU ou Busybox), poderá combiná-lo com a cauda:

tac | tail -n +3 | tac

Portably, você pode usar um filtro awk (não testado):

awk -vskip=2 '{
    lines[NR] = $0;
    if (NR > skip) print lines[NR-skip];
    delete lines[NR-skip];
}'

Se você deseja remover as últimas linhas de um arquivo grande, pode determinar o deslocamento em bytes da peça a ser truncada e executar a truncagem com dd.

total=$(wc -c < /file/to/truncate)
chop=$(tail -n 42 /file/to/truncate | wc -c)
dd if=/dev/null of=/file/to/truncate seek=1 bs="$((total-chop))"

Você não pode truncar um arquivo no lugar no início, mas se precisar remover as primeiras linhas de um arquivo enorme, poderá mover o conteúdo .

Gilles 'SO- parar de ser mau'
fonte
Em alguns sistemas (como o Linux moderno), você pode truncar (recolher) um arquivo em vigor no início, mas geralmente apenas em uma quantidade que seja múltipla do tamanho do bloco FS (portanto, não é realmente útil neste caso).
Stéphane Chazelas
3

Na tailpágina do manual (GNU tail, ou seja):

-n, --lines=K
   output the last K lines, instead of the last 10; or use -n +K to
   output lines starting with the Kth

Portanto, o seguinte deve anexar todas, exceto as 2 primeiras linhas de somefile.txta anotherfile.txt:

tail --lines=+3 somefile.txt >> anotherfile.txt
Steven segunda-feira
fonte
3

Para remover as primeiras n linhas, o GNU sed pode ser usado. Por exemplo, se n = 2

sed -n '1,2!p' input-file

A !média "excluir este intervalo". Como você pode imaginar, resultados mais complicados podem ser obtidos, por exemplo

sed -n '3,5p;7p'

que mostrará a linha 3,4,5,7. Mais poder vem do uso de expressões regulares em vez de endereços.

A limitação é que os números das linhas devem ser conhecidos antecipadamente.

enzotib
fonte
11
Por que não apenas sed 1,2d? Mais simples é geralmente melhor. Além disso, nada nos seus exemplos é específico ao GNU Sed; todos os seus comandos usam recursos padrão POSIX do Sed .
Curinga
1

Você pode usar diffpara comparar a saída de head/ tailpara o arquivo original e remover o que é o mesmo, obtendo assim o inverso.

diff --unchanged-group-format='' foo.txt <(head -2 foo.txt)
sokoban
fonte
1

Embora tail -n +4a saída do arquivo iniciando na 4ª linha (exceto as 3 primeiras) seja padrão e portátil, sua headcontraparte ( head -n -3exceto todas as últimas 3 linhas) não é.

Portably, você faria:

sed '$d' | sed '$d' | sed '$d'

Ou:

sed -ne :1 -e '1,3{N;b1' -e '}' -e 'P;N;D'

(lembre-se de que em alguns sistemas onde sedhá um espaço padrão de tamanho limitado, isso não é dimensionado para valores grandes de n).

Ou:

awk 'NR>3 {print l[NR%3]}; {l[NR%3]=$0}'
Stéphane Chazelas
fonte
1
{   head -n2 >/dev/null
    cat  >> other_document
}   <infile

Se <infileé um lseek()arquivo normal e possível, então sim , fique à vontade. A descrição acima é uma construção totalmente compatível com POSIX.

mikeserv
fonte
0

Minha abordagem é semelhante à de Gilles, mas, em vez disso, apenas inverto o arquivo com gato e cano que com o comando head.

tac -r thefile.txt | head thisfile.txt (substitui arquivos)

Abe
fonte
0

Espero ter entendido claramente sua necessidade.

Você tem várias maneiras de concluir sua solicitação:

tail -n$(expr $(cat /etc/passwd|wc -l) - 2) /etc/passwd

Onde / etc / passwd é seu arquivo

A segunda solução pode ser útil se você tiver um arquivo enorme:

my1stline=$(head -n1 /etc/passwd)
my2ndline=$(head -n2 /etc/passwd|grep -v "$my1stline")
cat /etc/passwd |grep -Ev "$my1stline|$my2ndline"
Mathieu Coavoux
fonte
0

Solução para BSD (macOS):

Remova as 2 primeiras linhas:

tail -n $( echo "$(cat foo.txt | wc -l)-2" | bc )

Remova as duas últimas linhas:

head -n $( echo "$(cat foo.txt | wc -l)-2" | bc )

... não é muito elegante, mas faz o trabalho!

espectro
fonte