Eu tenho uma linha (ou muitas linhas) de números delimitados por um caractere arbitrário. Quais ferramentas do UNIX posso usar para classificar os itens de cada linha numericamente, mantendo o delimitador?
Exemplos incluem:
- lista de números; entrada:
10 50 23 42
; classificados:10 23 42 50
- Endereço de IP; entrada:
10.1.200.42
; classificados:1.10.42.200
- CSV; entrada:
1,100,330,42
; classificados:1,42,100,330
- delimitado por tubos; entrada:
400|500|404
; classificados:400|404|500
Como o delimitador é arbitrário, sinta-se à vontade para fornecer (ou estender) uma Resposta usando um delimitador de um único caractere de sua escolha.
Com
gawk
( GNUawk
) para aasort()
função :substitua
*
como o separador de campoSEP='*'
pelo seu delimitador .Você também pode fazer com o seguinte comando no caso de uma única linha ( porque é melhor deixar de lado o uso de shell-loops para fins de processamento de texto )
substitua os pontos
.
pelo seu delimitador.adicione
-u
aosort
comando acima para remover as duplicatas.Notas:
Você pode precisar usar a
-g, --general-numeric-sort
opção desort
em vez de-n, --numeric-sort
para lidar com qualquer classe de números (inteiro, float, científico, hexadecimal, etc).Não
awk
há necessidade de mudança, ele ainda irá lidar com isso.Usando
perl
há uma versão óbvia; divida os dados, classifique-os, junte-os novamente.O delimitador precisa ser listado duas vezes (uma vez no
split
e uma vez nojoin
)por exemplo, para um
,
Então
Como o
split
é um regex, o caractere pode precisar de aspas:Usando as opções
-a
e-F
é possível remover a divisão. Com o-p
loop, como antes e defina os resultados como$_
, que imprimirá automaticamente:Usando Python e uma ideia semelhante à da resposta de Stephen Harris :
Então algo como:
Infelizmente, ter que fazer o I/O manualmente torna isso muito menos elegante do que a versão Perl.
Usando
sed
para classificar octetos de um endereço IPsed
não tem umasort
função interna, mas se seus dados forem suficientemente restritos no intervalo (como com endereços IP), você pode gerar um script sed que implementa manualmente um tipo de bolha simples . O mecanismo básico é procurar por números adjacentes que estejam fora de ordem. Se os números estiverem fora de ordem, troque-os.O
sed
script em si contém dois comandos de pesquisa e troca para cada par de números fora de ordem: um para os dois primeiros pares de octetos (forçando um delimitador à direita para marcar o final do terceiro octeto) e um segundo para o terceiro par de octetos (termine com EOL). Se ocorrerem trocas, o programa se ramifica para o início do script, procurando por números fora de ordem. Caso contrário, ele sai.O script gerado é, em parte:
Essa abordagem codifica o ponto como o delimitador, que deve ser escapado, caso contrário, seria "especial" para a sintaxe da expressão regular (permitindo qualquer caractere).
Para gerar tal script sed, este loop fará:
Redirecione a saída desse script para outro arquivo, digamos
sort-ips.sed
.Uma execução de amostra pode se parecer com:
A variação a seguir no script de geração usa os marcadores de limite de palavra
\<
e\>
para eliminar a necessidade da segunda substituição. Isso também reduz o tamanho do script gerado de 1,3 MB para pouco menos de 900 KB, além de reduzir bastante o tempo de execução dosed
próprio (para cerca de 50% a 75% do original, dependendo de qualsed
implementação está sendo usada):Casca
Carregar um idioma de nível superior leva tempo.
Por algumas linhas, o próprio shell pode ser uma solução.
Podemos usar o comando externo
sort
, e do comandotr
. Um é bastante eficiente na classificação de linhas e o outro é eficaz para converter um delimitador em novas linhas:Isso precisa de bash por causa do uso de
<<<
only. Se isso for substituído por um here-doc, a solução é válida para posix.Isso é capaz de classificar campos com tabulações, espaços ou caracteres shell glob (
*
,?
,[
). Não novas linhas porque cada linha está sendo classificada.Altere
<<<"$2"
para<"$2"
processar nomes de arquivos e chame-o como:O delimitador é o mesmo para todo o arquivo. Se isso é uma limitação, poderia ser melhorado.
No entanto, um arquivo com apenas 6.000 linhas leva 15 segundos para ser processado. Na verdade, o shell não é a melhor ferramenta para processar arquivos.
awk
Para mais do que algumas linhas (mais do que alguns 10's) é melhor usar uma linguagem de programação real. Uma solução awk poderia ser:
O que leva apenas 0,2 segundos para o mesmo arquivo de 6.000 linhas mencionado acima.
Entenda que os
<"$2"
arquivos for podem ser alterados de volta<<<"$2"
para as linhas dentro das variáveis do shell.perl
A solução mais rápida é perl.
Se você deseja classificar um arquivo, altere
<<<"$a"
para simplesmente"$a"
e adicione-i
opções perl para tornar a edição do arquivo "no local":Script bash:
Exemplo:
Baseado em
Dividir string em uma matriz no Bash
Como classificar um array no Bash
Juntar elementos de um array?
Aqui alguns bash que adivinham o delimitador por si só:
Pode não ser muito eficiente nem limpo, mas funciona.
Use como
bash my_script.sh "00/00/18/29838/2"
.Retorna um erro quando o mesmo delimitador não é usado consistentemente ou quando dois ou mais delimitadores seguem um ao outro.
Se o delimitador usado for um caractere especial, ele será escapado (caso contrário,
sed
retornará um erro).This answer is based on a misunderstanding of the Q., but in some cases it happens to be correct anyway. If the input is entirely natural numbers, and has only one delimiter per-line, (as with the sample data in the Q.), it works correctly. It'll also handle files with lines that each have their own delimiter, which is a bit more than what was asked for.
This shell function
read
s from standard input, uses POSIX parameter substitution to find the specific delimiter on each line, (stored in$d
), and usestr
to replace$d
with a newline\n
andsort
s that line's data, then restores each line's original delimiters:Applied to the data given in the OP:
Output:
For arbitrary delimiters:
On an input like:
It gives:
A seguir, uma variação da resposta de Jeff no sentido de gerar um
sed
script que fará o Bubble sort, mas é suficientemente diferente para justificar sua própria resposta.A diferença é que, em vez de gerar O(n^2) expressões regulares básicas, isso gera O(n) expressões regulares estendidas. O script resultante terá cerca de 15 KB de tamanho. O tempo de execução do
sed
script é em frações de segundo (demora um pouco mais para gerar o script).Ele se restringe a ordenar inteiros positivos delimitados por pontos, mas não se limita ao tamanho dos inteiros (basta aumentar
255
no loop principal), nem ao número de inteiros. O delimitador pode ser alterado alterandodelim='.'
o código.Já fiz a minha cabeça para acertar as expressões regulares, então vou deixar a descrição dos detalhes para outro dia.
O script ficará mais ou menos assim:
The idea behind the generated regular expressions is to pattern match for numbers that are less than each integer; those two numbers would be out-of-order, and so are swapped. The regular expressions are grouped into several OR options. Pay close attention to the ranges appended to each item, sometimes they are
{0}
, meaning the immediately-previous item is to be omitted from the searching. The regex options, from left-to-right, match numbers that are smaller than the given number by:To spell out an example, take
101
(with additional spaces for readability):Here, the first alternation allows the numbers 100 through 100; the second alternation allows 0 through 99.
Another example is
154
:Here the first option allows 150 through 153; the second allows 100 through 149, and the last allows 0 through 99.
Testing four times in a loop:
Output: