Tentando classificar em dois campos, segundo depois primeiro

106

Estou tentando classificar em várias colunas. Os resultados não são os esperados.

Aqui estão meus dados (people.txt):

Simon Strange 62
Pete Brown 37
Mark Brown 46
Stefan Heinz 52
Tony Bedford 50
John Strange 51
Fred Bloggs 22
James Bedford 21
Emily Bedford 18
Ana Villamor 44
Alice Villamor 50
Francis Chepstow 56

O seguinte funciona corretamente:

bash-3.2$ sort -k2 -k3 <people.txt                                                                                                                    
Emily Bedford 18                                                                                                                                      
James Bedford 21                                                                                                                                      
Tony Bedford 50                                                                                                                                       
Fred Bloggs 22                                                                                                                                        
Pete Brown 37                                                                                                                                         
Mark Brown 46                                                                                                                                         
Francis Chepstow 56                                                                                                                                   
Stefan Heinz 52                                                                                                                                       
John Strange 51                                                                                                                                       
Simon Strange 62                                                                                                                                      
Ana Villamor 44                                                                                                                                       
Alice Villamor 50

Mas, o seguinte não funciona conforme o esperado:

bash-3.2$ sort -k2 -k1 <people.txt                                        
Emily Bedford 18                                                                                                                                      
James Bedford 21                                                                                                                                      
Tony Bedford 50                                                                                                                                       
Fred Bloggs 22                                                                                                                                        
Pete Brown 37                                                                                                                                         
Mark Brown 46                                                                                                                                         
Francis Chepstow 56                                                                                                                                   
Stefan Heinz 52                                                                                                                                       
John Strange 51                                                                                                                                       
Simon Strange 62                                                                                                                                      
Ana Villamor 44                                                                                                                                       
Alice Villamor 50

Eu estava tentando classificar por sobrenome e depois pelo primeiro nome, mas você verá que os Villamors não estão na ordem correta. Eu esperava classificar por sobrenome e, em seguida, quando os sobrenomes correspondiam, para classificar pelo primeiro nome.

Parece que há algo sobre como isso deve funcionar, eu não entendo. Eu poderia fazer isso de outra maneira, é claro (usando o awk), mas quero entender o tipo.

Estou usando o shell Bash padrão no Mac OS X.

atormentar
fonte

Respostas:

159

Uma especificação-chave como -k2significa levar em consideração todos os campos de 2 ao final da linha. Então Villamor 44acaba antes Villamor 50. Como essas duas não são iguais, a primeira comparação em sort -k2 -k1é suficiente para discriminar essas duas linhas e a segunda chave de classificação -k1não é chamada. Se os dois Villamors tivessem a mesma idade, -k1teriam feito com que fossem classificados pelo primeiro nome.

Para classificar por uma única coluna, use -k2,2como a especificação principal. Isso significa usar os campos de # 2 a # 2, ou seja, apenas o segundo campo.

sort -k2 -k3 <people.txté redundante: é equivalente a sort -k2 <people.txt. Para classificar por sobrenome, nome e idade, execute o seguinte comando:

sort -k2,2 -k1,1 <people.txt

ou equivalente, sort -k2,2 -k1 <people.txtpois existem apenas esses três campos e os separadores são os mesmos. De fato, você obterá o mesmo efeito sort -k2,2 <people.txt, porque sortusa a linha inteira como último recurso quando todas as chaves em um subconjunto de linhas são idênticas.

Observe também que o separador de campo padrão é a transição entre um espaço em branco e um espaço em branco; portanto, as chaves incluirão os espaços em branco à esquerda (no seu exemplo, para a primeira linha, a primeira chave será "Emily", mas a segunda chave " Bedford". -bopção para remover esses espaços em branco:

sort -b -k2,2 -k1,1

Isso também pode ser feito por chave adicionando o bsinalizador no final da especificação de início de chave:

sort -k2b,2 -k1,1 <people.txt

Mas algo a ter em mente: assim que você adicionar um tal bandeira com a especificação de chave, os sinalizadores globais (como -n, -r...) já não se aplicam a eles por isso é melhor evitar misturar per-chave bandeiras e bandeiras globais.

Gilles
fonte
6
Você acertou em cheio. Eu tinha assumido (uma coisa perigosa a fazer) que especificar -k1 significaria usar o campo 1, onde o campo termina no separador de campos padrão (espaço). Mas, como você indica claramente, a opção k espera que você especifique os pontos de início e parada da chave, que podem ou não ser um único campo. Sua solução funciona perfeitamente e, mais importante, estou claro por que isso acontece. Muito Obrigado.
Harry
Isso é ENORME. Muitas outras fontes sobre o KEYDEF falam sobre -k1 -k2 sem enfatizar a importância do COMMA no formato para limitar quais colunas são consideradas em cada etapa de classificação. Fiquei preso por horas até encontrar essa resposta. E a página de manual é confusa aqui. Não explica que os locais "iniciar e parar" são especificados com a notação de vírgula. Obrigado!
Jason Rohrer em
16

Com o GNU, sortvocê faz assim, não tem certeza sobre o MacOS:

sort -k2,2 -k1 <people.txt

Atualize de acordo com o comentário. Citado em man sort:

   -k, --key=KEYDEF
          sort via a key; KEYDEF gives location and type

   KEYDEF is F[.C][OPTS][,F[.C][OPTS]] for start and stop position, where
   F is a field number and C a character position in the field; both are
   origin 1, and the stop position defaults to the line's end.
homem a trabalhar
fonte
4
Poderia, por favor, explicar essa notação estranha?
Scai
11
Isso me fez pensar na linha certa - obrigado por isso. Mas você não precisa especificar o ponto de parada para o segundo -k. Isso é -k2,2 -k1,1 caso contrário, o ponto de parada é considerado como final da linha?
Harry
@TonyBedford, correto. Mas não especificar a posição de parada não alterará o resultado da sua entrada atual, mas forçará a consistência caso você tenha várias linhas com os campos 2 e 1. idênticos. Portanto, prefiro permitir que a última -kinclua o máximo possível.
manatwork
11
@manatwork Isso não deve ser necessário; se todos os campos especificados forem iguais, sortcomparará a linha inteira. Ou com o GNU sortvocê pode usar -spara classificação estável.
Augurar