Classifique um arquivo de texto pelo comprimento da linha, incluindo espaços


137

Eu tenho um arquivo CSV parecido com este

AS2345, ASDF1232, Mr. Plain Example, 110 Binary ave., Atlantis, RI, 12345, (999) 123-5555,1,56
AS2345, ASDF1232, Sra. Plain Example, 1121110 Ternary st. 110 Binary ave .., Atlantis, RI, 12345, (999) 123-5555,1,56
AS2345, ASDF1232, Mr. Plain Example, 110 Binary ave., Liberty City, RI, 12345, (999) 123-5555,1,56
AS2345, ASDF1232, Mr. Plain Example, 110 Ternary ave., Some City, RI, 12345, (999) 123-5555,1,56

Preciso classificá-lo por comprimento de linha, incluindo espaços. O comando a seguir não inclui espaços. Existe uma maneira de modificá-lo para que funcione para mim?

cat $@ | awk '{ print length, $0 }' | sort -n | awk '{$1=""; print $0}'

21
Eu realmente gostaria de viver em Binary Avenue ou ternário Street, essas pessoas certamente concordaria com coisas como "8192 é um número redondo"
schnaader

Respostas:


224

Responda

cat testfile | awk '{ print length, $0 }' | sort -n -s | cut -d" " -f2-

Ou, para fazer sua sub-classificação original (talvez não intencional) de qualquer linha de igual comprimento:

cat testfile | awk '{ print length, $0 }' | sort -n | cut -d" " -f2-

Em ambos os casos, resolvemos o seu problema declarado, afastando o awk para o seu corte final.

Linhas de comprimento correspondente - o que fazer em caso de empate:

A pergunta não especificou se era necessária uma classificação adicional para linhas de comprimento correspondente. Eu assumi que isso é indesejável e sugeri o uso de -s( --stable) para impedir que essas linhas sejam classificadas uma contra a outra e mantenha-as na ordem relativa em que elas ocorrem na entrada.

(Aqueles que desejam mais controle sobre a classificação desses vínculos podem procurar a --keyopção de classificação .)

Por que a tentativa de solução da pergunta falha (awk line-reconstruction):

É interessante notar a diferença entre:

echo "hello   awk   world" | awk '{print}'
echo "hello   awk   world" | awk '{$1="hello"; print}'

Eles produzem respectivamente

hello   awk   world
hello awk world

A seção relevante do manual (gawk's) menciona apenas um aparte que o awk reconstruirá todo o valor de US $ 0 (com base no separador, etc.) quando você alterar um campo. Eu acho que não é um comportamento louco. Tem o seguinte:

"Finalmente, há momentos em que é conveniente forçar o awk a reconstruir todo o registro, usando o valor atual dos campos e do OFS. Para fazer isso, use a atribuição aparentemente inócua:"

 $1 = $1   # force record to be reconstituted
 print $0  # or whatever else with $0

"Isso força o awk a reconstruir o registro."

Entrada de teste, incluindo algumas linhas de igual comprimento:

aa A line   with     MORE    spaces
bb The very longest line in the file
ccb
9   dd equal len.  Orig pos = 1
500 dd equal len.  Orig pos = 2
ccz
cca
ee A line with  some       spaces
1   dd equal len.  Orig pos = 3
ff
5   dd equal len.  Orig pos = 4
g

1
heemayl, sim, obrigado. Tentei igualar a forma da solução da OP, sempre que possível, para permitir que ele se concentrasse apenas nas diferenças importantes entre a dele e a minha.
neillb

1
Vale ressaltar que também cat $@está quebrado. Você definitivamente definitivamente quer citá-lo, comocat "$@"
triplee

27

A solução AWK da neillb é ótima se você realmente deseja usar awke explica por que é um aborrecimento, mas se o que você quer é fazer o trabalho rapidamente e não se importar com o que faz, uma solução é usar sort()Função do Perl com uma rotina caparison personalizada para iterar nas linhas de entrada. Aqui está um liner:

perl -e 'print sort { length($a) <=> length($b) } <>'

Você pode colocar isso no seu pipeline sempre que precisar, recebendo STDIN (de catou um redirecionamento de shell) ou apenas dando o nome do arquivo para perl como outro argumento e deixe abrir o arquivo.

No meu caso, eu precisava das linhas mais longas primeiro, então troquei $ae $bcomparei.


Esta é uma solução melhor porque o awk causa uma classificação inesperada quando o arquivo de entrada contém linhas numéricas e alfanuméricas. Aqui o comando oneline: $ cat testfile | perl -e 'print sort {length ($ a) <=> length ($ b)} <>'
alemol

Rápido! Ficou com 465.000 arquivos de linha (uma palavra por linha) em <1 segundo, quando a saída foi redirecionada para outro arquivo - assim:cat testfile.txt | perl -e 'print sort { length($a) <=> length($b) } <>' > out.txt
cssyphus

Windows com StrawberryPerl funciona:type testfile.txt | perl -e "print sort { length($a) <=> length($b) } <>" > out.txt
bryc

14

Tente este comando:

awk '{print length, $0}' your-file | sort -n | cut -d " " -f2-

10

Resultados de referência

Abaixo estão os resultados de uma referência entre as soluções de outras respostas a esta pergunta.

Método de teste

  • 10 execuções seqüenciais em uma máquina rápida, com média de
  • Perl 5.24
  • awk 3.1.5 (vezes gawk 4.1.0 foram ~ 2% mais rápidas)
  • O arquivo de entrada é uma monstruosidade de 550 MB e 6 milhões de linhas (British National Corpus txt)

Resultados

  1. A perlsolução de Caleb levou 11,2 segundos
  2. minha perlsolução levou 11,6 segundos
  3. a awksolução nº 1 de neillb levou 20 segundos
  4. a awksolução nº 2 de neillb levou 23 segundos
  5. a awksolução de anubhava levou 24 segundos
  6. A awksolução de Jonathan levou 25 segundos
  7. A bashsolução da Fretz leva 400x mais tempo do que as awksoluções (usando um caso de teste truncado de 100000 linhas). Funciona bem, leva apenas uma eternidade.

perlOpção extra

Além disso, adicionei outra solução Perl:

perl -ne 'push @a, $_; END{ print sort { length $a <=> length $b } @a }' file

6

Pure Bash:

declare -a sorted

while read line; do
  if [ -z "${sorted[${#line}]}" ] ; then          # does line length already exist?
    sorted[${#line}]="$line"                      # element for new length
  else
    sorted[${#line}]="${sorted[${#line}]}\n$line" # append to lines with equal length
  fi
done < data.csv

for key in ${!sorted[*]}; do                      # iterate over existing indices
  echo -e "${sorted[$key]}"                       # echo lines with equal length
done

3

A length()função inclui espaços. Eu faria apenas pequenos ajustes no seu pipeline (incluindo evitar o UUOC ).

awk '{ printf "%d:%s\n", length($0), $0;}' "$@" | sort -n | sed 's/^[0-9]*://'

O sedcomando remove diretamente os dígitos e dois pontos adicionados pelo awkcomando. Como alternativa, mantendo sua formatação de awk:

awk '{ print length($0), $0;}' "$@" | sort -n | sed 's/^[0-9]* //'

2

Descobri que essas soluções não funcionarão se o seu arquivo contiver linhas que começam com um número, pois serão classificadas numericamente junto com todas as linhas contadas. A solução é fornecer sorto -gsinalizador -n( classificação numérica geral) em vez de (classificação numérica):

awk '{ print length, $0 }' lines.txt | sort -g | cut -d" " -f2-

2
Olá, Markus. Não observo o conteúdo da linha (numérico ou não) - em oposição ao comprimento da linha - como tendo qualquer efeito na classificação, exceto no caso de linhas com comprimentos correspondentes. É isso que você queria dizer? Nesses casos, não achei que a alternância dos métodos de classificação -npara os sugeridos -gresultasse em melhorias, portanto espero que não. Agora, na minha resposta, eu lidei como proibir a classificação secundária de linhas de comprimento igual (usando --stable). Quer fosse ou não o que você quis dizer, obrigado por chamar minha atenção! Também adicionei uma entrada considerada para testar.
neillb

4
Não, deixe-me explicar detalhadamente. Apenas a awkpeça irá gerar uma lista de linhas prefixadas com o comprimento da linha e um espaço. A tubulação sort -nfuncionará conforme o esperado. Mas se alguma dessas linhas já tiver um número no início, essas linhas começarão com comprimento + espaço + número. sort -ndesconsidera esse espaço e o trata como um número concatenado de comprimento + número. O uso da -gbandeira será interrompido no primeiro espaço, produzindo uma classificação correta. Tente você mesmo criando um arquivo com algumas linhas com prefixo numérico e execute o comando passo a passo.
Markus Amalthea Magnuson

1
Também descobri que isso sort -ndesconsidera o espaço e produz uma classificação incorreta. sort -ggera a ordem correta.
Robert Smith

Não consigo reproduzir o problema descrito com -nin sort (GNU coreutils) 8.21. A infodocumentação é descrita -gcomo menos eficiente e potencialmente menos precisa (converte números em flutuadores); portanto, provavelmente não a use se não for necessário.
Phill

documentação para -n: "Classificar numericamente. O número inicia cada linha e consiste em espaços em branco opcionais, um sinal '-' opcional e zero ou mais dígitos possivelmente separados por separadores de milhares, opcionalmente seguido por um caractere de ponto decimal e zero ou mais dígitos . Um número vazio é tratado como '0'. O código de idioma 'LC_NUMERIC' especifica o caractere de ponto decimal e o separador de milhares. Por padrão, um espaço em branco é um espaço ou uma guia, mas o código de idioma 'LC_CTYPE' pode alterar isso. "
Phill


2

1) solução awk pura. Vamos supor que o comprimento da linha não possa ser maior que 1024

nome do arquivo cat | awk 'COMEÇA {min = 1024; s = "";} {l = comprimento ($ 0); se (l <min) {min = l; s = $ 0;}} END {print s} '

2) uma solução bash de liner assumindo que todas as linhas têm apenas 1 palavra, mas pode ser retrabalhada para qualquer caso em que todas as linhas tenham o mesmo número de palavras:

LINHAS = $ (nome do arquivo do gato); para k em $ LINES; faça printf "$ k"; eco $ k | wc -L; feito | classificar -k2 | cabeça -n 1 | cut -d "" -f1


1

Aqui está um método compatível com vários bytes de classificação de linhas por comprimento. Isso requer:

  1. wc -m está disponível para você (o macOS possui).
  2. Seu código do idioma atual suporta caracteres de vários bytes, por exemplo, definindo LC_ALL=UTF-8. Você pode configurá-lo no seu .bash_profile ou simplesmente acrescentando-o antes do comando a seguir.
  3. testfile possui uma codificação de caracteres que corresponde ao seu código do idioma (por exemplo, UTF-8).

Aqui está o comando completo:

cat testfile | awk '{l=$0; gsub(/\047/, "\047\"\047\"\047", l); cmd=sprintf("echo \047%s\047 | wc -m", l); cmd | getline c; close(cmd); sub(/ */, "", c); { print c, $0 }}' | sort -ns | cut -d" " -f2-

Explicando parte por parte:

  • l=$0; gsub(/\047/, "\047\"\047\"\047", l);← faz uma cópia de cada linha na variável awk le escapa duas vezes a cada, 'para que a linha possa ecoar com segurança como um comando shell ( \047é uma aspas simples na notação octal).
  • cmd=sprintf("echo \047%s\047 | wc -m", l);← este é o comando que executaremos, que ecoa a linha escapada para wc -m.
  • cmd | getline c;← executa o comando e copia o valor da contagem de caracteres retornado para a variável awk c.
  • close(cmd); ← feche o canal no comando shell para evitar atingir um limite do sistema no número de arquivos abertos em um processo.
  • sub(/ */, "", c);← corta o espaço em branco do valor da contagem de caracteres retornado por wc.
  • { print c, $0 } ← imprime o valor da contagem de caracteres da linha, um espaço e a linha original.
  • | sort -ns← classifica as linhas (pelos valores de contagem de caracteres acrescentados) numericamente ( -n) e mantém a ordem de classificação estável ( -s).
  • | cut -d" " -f2- ← remove os valores de contagem de caracteres anexados.

É lento (apenas 160 linhas por segundo em um Macbook Pro veloz) porque deve executar um subcomando para cada linha.

Como alternativa, faça isso apenas com gawk(a partir da versão 3.1.5, o gawk reconhece multibytes), o que seria significativamente mais rápido. É muito difícil fazer todas as escapadas e aspas duplas para passar com segurança as linhas através de um comando shell do awk, mas esse é o único método que eu pude encontrar que não requer a instalação de software adicional (o gawk não está disponível por padrão em Mac OS).

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.