Existe uma maneira de ignorar as linhas de cabeçalho em uma classificação UNIX?


102

Eu tenho um arquivo de campo de largura fixa que estou tentando classificar usando o utilitário de classificação UNIX (Cygwin, no meu caso).

O problema é que há um cabeçalho de duas linhas na parte superior do arquivo que está sendo classificado na parte inferior do arquivo (pois cada linha de cabeçalho começa com dois pontos).

Existe uma maneira de dizer à classificação "passar as duas primeiras linhas entre não classificados" ou de especificar uma ordem que classifique as linhas de dois pontos no topo - as linhas restantes são sempre iniciadas com um numérico de 6 dígitos (que é na verdade a chave I estou classificando) se isso ajudar.

Exemplo:

:0:12345
:1:6:2:3:8:4:2
010005TSTDOG_FOOD01
500123TSTMY_RADAR00
222334NOTALINEOUT01
477821USASHUTTLES21
325611LVEANOTHERS00

deve classificar para:

:0:12345
:1:6:2:3:8:4:2
010005TSTDOG_FOOD01
222334NOTALINEOUT01
325611LVEANOTHERS00
477821USASHUTTLES21
500123TSTMY_RADAR00

Para registro: a linha de comando que estou usando até agora é "sort -t \\ -k1.1,1.6 <file>" [os dados podem conter espaços, mas nunca conterão uma barra invertida]
Rob Gilliam

Respostas:


125
(head -n 2 <file> && tail -n +3 <file> | sort) > newfile

Os parênteses criam um subshell, envolvendo o stdout para que você possa canalizá-lo ou redirecioná-lo como se tivesse vindo de um único comando.


Obrigado; Estou aceitando esta resposta porque parece mais completa e concisa (e eu entendo o que está fazendo!) - deveria ser "head -n 2", embora :-)
Rob Gilliam

1
Obrigado, consertou a parte da 'cabeça'.
BobS

4
Existe uma maneira de fazer esta versão funcionar em dados canalizados? Tentei com tee >(head -n $header_size) | tail -n +$header_size | sort, mas a cabeça parece correr atrás do tail|sorttubo, então o cabeçalho acaba impresso no final. Isso é determinístico ou uma condição de corrida?
Damien Pollet

Você provavelmente poderia juntar as peças de algo onde usar catpara redirecionar o stdin para um arquivo temporário e, em seguida, executar o comando acima nesse novo arquivo, mas está começando a ficar feio o suficiente que é provavelmente melhor usar uma das soluções baseadas em awk fornecidas em as outras respostas.
BobS

@DamienPollet: Veja a resposta de Dave .
Jonathan Leffler

63

Se você não se importa de usar awk, pode aproveitarawk capacidades de tubulação integradas do

por exemplo.

extract_data | awk 'NR<3{print $0;next}{print $0| "sort -r"}' 

Isso imprime as duas primeiras linhas textualmente e canaliza o resto através sort .

Observe que isso tem a vantagem muito específica de poder classificar seletivamente as partes de uma entrada canalizada. todos os outros métodos sugeridos apenas classificarão arquivos simples que podem ser lidos várias vezes. Isso funciona em qualquer coisa.


2
Muito bom, e funciona com tubos arbitrários, não apenas com arquivos!
lapo

4
Linda, o awk nunca para de me surpreender. Além disso, você não precisa do $0, printé o suficiente.
nachocab de

1
A resposta de @SamWatkins freeseek é menos feia.
fess.

O que a opção -r está fazendo para classificar? Isso deveria ser classificação reversa?
gvrocha

32

Esta é uma versão que funciona em dados canalizados:

(read -r; printf "%s\n" "$REPLY"; sort)

Se o seu cabeçalho tiver várias linhas:

(for i in $(seq $HEADER_ROWS); do read -r; printf "%s\n" "$REPLY"; done; sort)

Esta solução é daqui


9
legais. para o caso de cabeçalho único que eu uso extract_data | (read h; echo "$h"; sort) , é curto o suficiente para lembrar. seu exemplo cobre mais casos extremos. :) Esta é a melhor resposta. funciona em tubos. não awk.
fess.

1
Ok, eu tracei isso e parece que o bash faz de tudo para fazer isso funcionar. Em geral, se você codificasse em C ou em outra linguagem, não funcionaria porque stdio leria mais do que apenas a primeira linha do cabeçalho. Se você executá-lo em um arquivo pesquisável, o bash lê um trecho maior (128 bytes em meu teste) e, em seguida, volta para depois do final da primeira linha. Se você executá-lo em um tubo, o bash lê um caractere de cada vez até passar o fim da linha.
Sam Watkins

Agradável! Se você quiser apenas comer o cabeçalho, é ainda mais fácil lembrar:extract_data | (read; sort)
Jason Suárez

Este é quase perfeito, mas você precisa usar "IFS = read" em vez de "read" para manter os espaços à esquerda e à direita.
Stanislav German-Evtushenko

6
Esta deve ser a resposta aceita em minha opinião. Simples, conciso e mais flexível, pois também funciona em dados canalizados.
Paulo I

12

Em casos simples, sedpode fazer o trabalho com elegância:

    your_script | (sed -u 1q; sort)

ou equivalente,

    cat your_data | (sed -u 1q; sort)

A chave está em 1q- imprime a primeira linha (cabeçalho) e sai (deixando o resto da entrada parasort ).

Para o exemplo dado, 2q fará o truque.

A -uopção (sem buffer) é necessária para aqueles seds (notavelmente, GNU's) que, de outra forma, leriam a entrada em blocos, consumindo, assim, os dados que você deseja acessar sort.


1
Olá, @Andrea; bem-vindo ao Stack Overflow. Receio que sua resposta não funcione, pelo menos não quando estou testando no Git Bash no Windows (mudei do Cygwin, o shell que estava usando um trabalho diferente 6 anos atrás). O comando sed puxa todos os dados do stdin, não deixando nenhum dado passar para a classificação. Tente alterar o comando para cat your_data | (sed 1q; wc -l) para ver o que quero dizer.
Rob Gilliam

1
Isso pode funcionar se você passar a entrada uma segunda vez para o comando sed, como este: cat sortMe.csv | (sed 1q sortMe.csv; sort -t, -k3 -rn)> Sort.csv
Harry Cramer

8

Você pode usar tail -n +3 <file> | sort ...(tail produzirá o conteúdo do arquivo da 3ª linha).


4
head -2 <your_file> && nawk 'NR>2' <your_file> | sort

exemplo:

> cat temp
10
8
1
2
3
4
5
> head -2 temp && nawk 'NR>2' temp | sort -r
10
8
5
4
3
2
1

3

Leva apenas 2 linhas de código ...

head -1 test.txt > a.tmp; 
tail -n+2 test.txt | sort -n >> a.tmp;

Para dados numéricos, -n é obrigatório. Para classificação alfa, o -n não é necessário.

Arquivo de exemplo:
$ cat test.txt

cabeçalho
8
5
100
1
-1

Resultado:
$ cat a.tmp

cabeçalho
-1
1
5
8
100


1
Não é basicamente a mesma resposta que a resposta aceita? (Exceto que a abordagem do BobS coloca o resultado em stdout, permitindo que você envie o resultado por meio de outros filtros antes de ser gravado no arquivo, se necessário)
Rob Gilliam

1

Então aqui está uma função bash onde os argumentos são exatamente como sort. Arquivos e canais de suporte.

function skip_header_sort() {
    if [[ $# -gt 0 ]] && [[ -f ${@: -1} ]]; then
        local file=${@: -1}
        set -- "${@:1:$(($#-1))}"
    fi
    awk -vsargs="$*" 'NR<2{print; next}{print | "sort "sargs}' $file
}

Como funciona. Esta linha verifica se há pelo menos um argumento e se o último argumento é um arquivo.

    if [[ $# -gt 0 ]] && [[ -f ${@: -1} ]]; then

Isso salva o arquivo para separar o argumento. Já que estamos prestes a apagar o último argumento.

        local file=${@: -1}

Aqui removemos o último argumento. Já que não queremos passá-lo como um argumento de classificação.

        set -- "${@:1:$(($#-1))}"

Finalmente, fazemos a parte do awk, passando os argumentos (menos o último argumento se for o arquivo) para classificar no awk. Isso foi originalmente sugerido por Dave e modificado para aceitar argumentos de classificação. Contamos com o fato de que $fileestará vazio se estivermos encanando, portanto, será ignorado.

    awk -vsargs="$*" 'NR<2{print; next}{print | "sort "sargs}' $file

Exemplo de uso com um arquivo separado por vírgulas.

$ cat /tmp/test
A,B,C
0,1,2
1,2,0
2,0,1

# SORT NUMERICALLY SECOND COLUMN
$ skip_header_sort -t, -nk2 /tmp/test
A,B,C
2,0,1
0,1,2
1,2,0

# SORT REVERSE NUMERICALLY THIRD COLUMN
$ cat /tmp/test | skip_header_sort -t, -nrk3
A,B,C
0,1,2
2,0,1
1,2,0

0

Com Python:

import sys
HEADER_ROWS=2

for _ in range(HEADER_ROWS):
    sys.stdout.write(next(sys.stdin))
for row in sorted(sys.stdin):
    sys.stdout.write(row)

pressupõe que o sistema tenha Python instalado (o meu não)
Rob Gilliam

0

Aqui está uma função shell bash derivada de outras respostas. Ele lida com arquivos e canais. O primeiro argumento é o nome do arquivo ou '-' para stdin. Os argumentos restantes são passados ​​para classificação. Alguns exemplos:

$ hsort myfile.txt
$ head -n 100 myfile.txt | hsort -
$ hsort myfile.txt -k 2,2 | head -n 20 | hsort - -r

A função shell:

hsort ()
{
   if [ "$1" == "-h" ]; then
       echo "Sort a file or standard input, treating the first line as a header.";
       echo "The first argument is the file or '-' for standard input. Additional";
       echo "arguments to sort follow the first argument, including other files.";
       echo "File syntax : $ hsort file [sort-options] [file...]";
       echo "STDIN syntax: $ hsort - [sort-options] [file...]";
       return 0;
   elif [ -f "$1" ]; then
       local file=$1;
       shift;
       (head -n 1 $file && tail -n +2 $file | sort $*);
   elif [ "$1" == "-" ]; then
       shift;
       (read -r; printf "%s\n" "$REPLY"; sort $*);
   else
       >&2 echo "Error. File not found: $1";
       >&2 echo "Use either 'hsort <file> [sort-options]' or 'hsort - [sort-options]'";
       return 1 ;
   fi
}

0

Esta é a mesma resposta de Ian Sherbin, mas minha implementação é: -

cut -d'|' -f3,4,7 $arg1 | uniq > filetmp.tc
head -1 filetmp.tc > file.tc;
tail -n+2 filetmp.tc | sort -t"|" -k2,2 >> file.tc;

-4
cat file_name.txt | sed 1d | sort 

Isso fará o que você quiser.


1) Isso apenas remove a linha do cabeçalho e classifica o resto, não classifica tudo abaixo da linha do cabeçalho, deixando o cabeçalho intacto. 2) remove apenas a primeira linha, quando o cabeçalho tem na verdade duas linhas (leia a pergunta). 3) Por que você usa "cat file_name.txt | sed 1d" quando "sed 1d <file_name.txt" ou mesmo apenas "sed 1d file_name.txt" tem o mesmo efeito?
Rob Gilliam,
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.