classifique, mas mantenha a linha do cabeçalho no topo

56

Estou obtendo saída de um programa que primeiro produz uma linha que é um monte de cabeçalhos de coluna e, em seguida, um monte de linhas de dados. Quero cortar várias colunas dessa saída e visualizá-la classificada de acordo com várias colunas. Sem os cabeçalhos, o corte e a classificação são facilmente realizados através da -kopção de sortacompanhar cutou awkvisualizar um subconjunto das colunas. No entanto, esse método de classificação combina os cabeçalhos das colunas com o restante das linhas de saída. Existe uma maneira fácil de manter os cabeçalhos no topo?

jonderry
fonte
11
Me deparei com o seguinte link . No entanto, não consigo fazer essa técnica { head -1; sort; }funcionar. Ele sempre exclui um monte de texto após a primeira linha. Alguém sabe por que isso acontece?
jonderry
11
Eu suspeito que é porque headestá lendo mais de uma linha em um buffer e jogando a maior parte fora. Minha sedideia teve o mesmo problema.
Andy
@ jonderry - essa técnica funciona apenas com lseekentrada capaz, portanto não funciona ao ler de um tubo. Ele vai funcionar se você redirecionar para um arquivo >outfilee execute{ head -n 1; sort; } <outfile
don_crissti

Respostas:

58

Roubar a ideia de Andy e transformá-la em uma função para facilitar o uso:

# print the header (the first line of input)
# and then run the specified command on the body (the rest of the input)
# use it in a pipeline, e.g. ps | body grep somepattern
body() {
    IFS= read -r header
    printf '%s\n' "$header"
    "$@"
}

Agora eu posso fazer:

$ ps -o pid,comm | body sort -k2
  PID COMMAND
24759 bash
31276 bash
31032 less
31177 less
31020 man
31167 man
...

$ ps -o pid,comm | body grep less
  PID COMMAND
31032 less
31177 less
Mikel
fonte
ps -C COMMANDpode ser mais apropriado que grep COMMAND, mas é apenas um exemplo. Além disso, você não pode usar -Cse também tiver usado outra opção de seleção como -U.
23611 Mikel
Ou talvez deva ser chamado body? Como em body sortou body grep. Pensamentos?
23611 Mikel
3
Renomeado de headerpara body, porque você está fazendo a ação no corpo. Espero que isso faça mais sentido.
23611 Mikel
2
Lembre-se de convidar bodytodos os participantes subsequentes do pipeline:ps -o pid,comm | body grep less | body sort -k1nr
bispo
11
@ Tim Você pode simplesmente escrever <foo body sort -k2ou body sort -k2 <foo. Apenas um caractere extra do que você queria.
Mikel
37

Você pode manter o cabeçalho no topo assim com o bash:

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

Ou faça-o com perl:

command | perl -e 'print scalar (<>); print sort { ... } <>'
Andy
fonte
2
+1 impressionante. Vale a pena agrupar-se como uma função de shell, eu acho.
23611 Mikel
11
+1, qualquer motivo pelo qual um subshell é preferível ou está {}ok em vez de ()?
precisa saber é o seguinte
2
IFS=desativa a divisão de palavras ao ler a entrada. Eu não acho que é necessário ao ler $REPLY. echoirá expandir as barras invertidas se xpg_echoestiver definido (não o padrão); printfé mais seguro nesse caso. echo $REPLYsem aspas condensará espaços em branco; Eu acho que echo "$REPLY"deveria ficar bem. read -rserá necessário se a entrada puder conter escapes de barra invertida. Parte disso pode depender da versão do bash.
Andy
11
@ Andy: Uau, você está certo, regras diferentes para read REPLY; echo $REPLY(retira espaços à esquerda) e read; echo $REPLY(não).
23611 Mikel
11
@ Andy: IIRC, o valor padrão xpg_echodepende do seu sistema, por exemplo, no Solaris, acho que o padrão é verdadeiro. É por isso que Gilles gosta printftanto: é a única coisa com comportamento previsível.
23611 Mikel
23

Encontrei uma boa versão do awk que funciona muito bem em scripts:

awk 'NR == 1; NR > 1 {print $0 | "sort -n"}'
Michael Kuhn
fonte
11
Eu gosto disso, mas requer um pouco de explicação - o pipe está dentro do script awk. Como isso funciona? Está chamando o sortcomando externamente? Alguém conhece pelo menos um link para uma página que explica o uso de pipe no awk?
Curinga
@ Cartão virtual, você pode verificar a página do manual oficial ou este manual .
Lapo
4

Hackish, mas eficaz: preceda 0todas as linhas do cabeçalho e 1todas as outras linhas antes da classificação. Retire o primeiro caractere após a classificação.

… |
awk '{print (NR <= 2 ? "0 " : "1 ") $0}' |
sort -k 1 -k… |
cut -b 3-
Gilles 'SO- parar de ser mau'
fonte
3

Aqui estão alguns ruídos mágicos da linha perl nos quais você pode canalizar sua saída para classificar tudo, mas manter a primeira linha no topo: perl -e 'print scalar <>, sort <>;'

Ryan Thompson
fonte
2

Tentei a command | {head -1; sort; }solução e posso confirmar que realmente estraga tudo - headlê em várias linhas do tubo e depois produz apenas o primeiro. Portanto, o restante da saída, que head não leu, é passado para - sortNÃO o restante da saída começando na linha 2!

O resultado é que faltam linhas (e uma linha parcial!) Que estavam no início de sua saída de comando (exceto que você ainda possui a primeira linha) - um fato que é fácil de confirmar adicionando um canal wcno final de o pipeline acima - mas isso é extraordinariamente difícil de rastrear se você não sabe disso! Passei pelo menos 20 minutos tentando descobrir por que tinha uma linha parcial (primeiros 100 bytes mais ou menos cortados) na minha saída antes de resolvê-la.

O que acabei fazendo, que funcionou lindamente e não exigiu a execução do comando duas vezes, foi:

myfile=$(mktemp)
whatever command you want to run > $myfile

head -1 $myfile
sed 1d $myfile | sort

rm $myfile

Se você precisar colocar a saída em um arquivo, poderá modificá-lo para:

myfile=$(mktemp)
whatever command you want to run > $myfile

head -1 $myfile > outputfile
sed 1d $myfile | sort >> outputfile

rm $myfile
Curinga
fonte
Você pode usar heado lineutilitário interno do ksh93 ou o utilitário (em sistemas que ainda possuem um) ou gnu-sed -u qou IFS=read -r line; printf '%s\n' "$line", que leem a entrada um byte por vez para evitar isso.
Stéphane Chazelas
1

Eu acho que isso é mais fácil.

ps -ef | ( head -n 1 ; sort )

ou isso que é possivelmente mais rápido, pois não cria um sub shell

ps -ef | { head -n 1 ; sort ; }

Outros usos legais

embaralhar linhas após a linha do cabeçalho

cat file.txt |  ( head -n 1 ; shuf )

reverter linhas após a linha do cabeçalho

cat file.txt |  ( head -n 1 ; tac )
user2449151
fonte
2
Consulte unix.stackexchange.com/questions/11856/… . Esta não é realmente uma boa solução.
Curinga
11
Não funciona, cat file | { head -n 1 ; sort ; } > file2só mostram cabeça
Peter Krauss
0
command | head -1; command | tail -n +2 | sort
Sarva
fonte
4
Isso começa commandduas vezes. Portanto, é limitado a alguns comandos específicos. No entanto, para o pscomando solicitado no exemplo, ele funcionaria.
Jofel
0

Simples e direto!

<command> | head -n 1; <command> | sed 1d | sort <....>
  • sed nd ---> 'n' especifica o número da linha e 'd' significa delete.
Jatsui
fonte
11
Assim como jofel comentou há um ano e meio sobre a resposta de Sarva, isso começa commandduas vezes. Portanto, não é realmente adequado para uso em um pipeline.
Curinga
0

Eu vim aqui procurando uma solução para o comando w. Este comando mostra detalhes de quem está conectado e o que está fazendo.

Para mostrar os resultados classificados, mas com os cabeçalhos mantidos no topo (existem duas linhas de cabeçalhos), decidi:

w | head -n 2; w | tail -n +3 | sort

Obviamente, isso executa o comando wduas vezes e, portanto, pode não ser adequado para todas as situações. No entanto, para sua vantagem, é substancialmente mais fácil de lembrar.

Note que o tail -n +3meio 'mostra todas as linhas a partir do 3º em diante' (veja man tailpara detalhes).

Robert
fonte
-2

Tente fazer:

wc -l file_name | tail -n $(awk '{print $1-1}') file_name | sort
Barry
fonte
3
Eu não entendi
Pierre.Vriens