AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / unix / Perguntas / 436102
Accepted
Jeff Schaller
Jeff Schaller
Asked: 2018-04-07 16:58:53 +0800 CST2018-04-07 16:58:53 +0800 CST 2018-04-07 16:58:53 +0800 CST

Como posso classificar numericamente uma única linha de itens delimitados?

  • 772

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.

sort numeric-data
  • 13 13 respostas
  • 5007 Views

13 respostas

  • Voted
  1. Best Answer
    αғsнιη
    2018-04-07T17:27:23+08:002018-04-07T17:27:23+08:00

    Com gawk( GNU awk ) para a asort()função :

    gawk -v SEP='*' '{ i=0; split($0, arr, SEP); len=asort(arr);
        while ( ++i<=len ){ printf("%s%s", i>1?SEP:"", arr[i]) }; 
            print "" 
    }' infile
    

    substitua *como o separador de campo SEP='*'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 )

    tr '.' '\n' <<<"$aline" | sort -n | paste -sd'.' -
    

    substitua os pontos . pelo seu delimitador.
    adicione -uao sortcomando acima para remover as duplicatas.

    Notas:
    Você pode precisar usar a -g, --general-numeric-sortopção de sortem vez de -n, --numeric-sortpara lidar com qualquer classe de números (inteiro, float, científico, hexadecimal, etc).

    $ aline='2e-18,6.01e-17,1.4,-4,0xB000,0xB001,23,-3.e+11'
    $ tr ',' '\n' <<<"$aline" |sort -g | paste -sd',' -
    -3.e+11,-4,2e-18,6.01e-17,1.4,23,0xB000,0xB001
    

    Não awkhá necessidade de mudança, ele ainda irá lidar com isso.

    • 15
  2. Stephen Harris
    2018-04-07T17:25:28+08:002018-04-07T17:25:28+08:00

    Usando perlhá uma versão óbvia; divida os dados, classifique-os, junte-os novamente.

    O delimitador precisa ser listado duas vezes (uma vez no splite uma vez no join)

    por exemplo, para um,

    perl -lpi -e '$_=join(",",sort {$a <=> $b} split(/,/))'
    

    Então

    echo 1,100,330,42 | perl -lpi -e '$_=join(",",sort {$a <=> $b} split(/,/))'
    1,42,100,330
    

    Como o splité um regex, o caractere pode precisar de aspas:

    echo 10.1.200.42 | perl -lpi -e '$_=join(".",sort {$a <=> $b} split(/\./))'
    1.10.42.200
    

    Usando as opções -ae -Fé possível remover a divisão. Com o -ploop, como antes e defina os resultados como $_, que imprimirá automaticamente:

    perl -F'/\./' -aple '$_=join(".", sort {$a <=> $b} @F)'
    
    • 12
  3. muru
    2018-04-07T18:02:34+08:002018-04-07T18:02:34+08:00

    Usando Python e uma ideia semelhante à da resposta de Stephen Harris :

    python3 -c 'import sys; c = sys.argv[1]; sys.stdout.writelines(map(lambda x: c.join(sorted(x.strip().split(c), key=int)) + "\n", sys.stdin))' <delmiter>
    

    Então algo como:

    $ cat foo
    10.129.3.4
    1.1.1.1
    4.3.2.1
    $ python3 -c 'import sys; c = sys.argv[1]; sys.stdout.writelines(map(lambda x: c.join(sorted(x.strip().split(c), key=int)) + "\n", sys.stdin))' . < foo
    3.4.10.129
    1.1.1.1
    1.2.3.4
    

    Infelizmente, ter que fazer o I/O manualmente torna isso muito menos elegante do que a versão Perl.

    • 6
  4. Jeff Schaller
    2018-04-07T16:58:53+08:002018-04-07T16:58:53+08:00

    Usando sedpara classificar octetos de um endereço IP

    sednão tem uma sortfunçã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 sedscript 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:

    $ head -n 3 generated.sed
    :top
    s/255\.254\./254.255./g; s/255\.254$/254.255/
    s/255\.253\./253.255./g; s/255\.253$/253.255/
    
    # ... middle of the script omitted ...
    
    $ tail -n 4 generated.sed
    s/2\.1\./1.2./g; s/2\.1$/1.2/
    s/2\.0\./0.2./g; s/2\.0$/0.2/
    s/1\.0\./0.1./g; s/1\.0$/0.1/
    ttop
    

    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á:

    #!/bin/bash
    
    echo ':top'
    
    for (( n = 255; n >= 0; n-- )); do
      for (( m = n - 1; m >= 0; m-- )); do
        printf '%s; %s\n' "s/$n\\.$m\\./$m.$n./g" "s/$n\\.$m\$/$m.$n/"
      done
    done
    
    echo 'ttop'
    

    Redirecione a saída desse script para outro arquivo, digamos sort-ips.sed.

    Uma execução de amostra pode se parecer com:

    ip=$((RANDOM % 256)).$((RANDOM % 256)).$((RANDOM % 256)).$((RANDOM % 256))
    printf '%s\n' "$ip" | sed -f sort-ips.sed
    

    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 do sedpróprio (para cerca de 50% a 75% do original, dependendo de qual sedimplementação está sendo usada):

    #!/bin/bash
    
    echo ':top'
    
    for (( n = 255; n >= 0; --n )); do
      for (( m = n - 1; m >= 0; --m )); do
          printf '%s\n' "s/\\<$n\\>\\.\\<$m\\>/$m.$n/g"
      done
    done
    
    echo 'ttop'
    
    • 4
  5. user232326
    2018-04-07T21:19:58+08:002018-04-07T21:19:58+08:00

    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 comando tr. Um é bastante eficiente na classificação de linhas e o outro é eficaz para converter um delimitador em novas linhas:

    #!/bin/bash
    shsort(){
               while IFS='' read -r line; do
                   echo "$line" | tr "$1" '\n' |
                   sort -n   | paste -sd "$1" -
               done <<<"$2"
        }
    
    shsort ' '    '10 50 23 42'
    shsort '.'    '10.1.200.42'
    shsort ','    '1,100,330,42'
    shsort '|'    '400|500|404'
    shsort ','    '3 b,2       x,45    f,*,8jk'
    shsort '.'    '10.128.33.6
    128.17.71.3
    44.32.63.1'
    

    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:

    shsort '.'    infile
    

    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:

    #!/bin/bash
    awksort(){
               gawk -v del="$1" '{
                   split($0, fields, del)
                   l=asort(fields)
                   for(i=1;i<=l;i++){
                       printf( "%s%s" , (i==0)?"":del , fields[i] )
                   }
                   printf "\n"
               }' <"$2"
             }
    
    awksort '.'    infile
    

    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.

    #!/bin/bash
    perlsort(){  perl -lp -e '$_=join("'"$1"'",sort {$a <=> $b} split(/['"$1"']/))' <<<"$2";   }
    
    perlsort ' '    '10 50 23 42'
    perlsort '.'    '10.1.200.42'
    perlsort ','    '1,100,330,42'
    perlsort '|'    '400|500|404'
    perlsort ','    '3 b,2       x,45    f,*,8jk'
    perlsort '.'    '10.128.33.6
    128.17.71.3
    44.32.63.1'
    

    Se você deseja classificar um arquivo, altere <<<"$a"para simplesmente "$a"e adicione -iopções perl para tornar a edição do arquivo "no local":

    #!/bin/bash
    perlsort(){  perl -lpi -e '$_=join("'"$1"'",sort {$a <=> $b} split(/['"$1"']/))' "$2"; }
    
    perlsort '.' infile; exit
    
    • 4
  6. Sergiy Kolodyazhnyy
    2018-04-07T22:14:34+08:002018-04-07T22:14:34+08:00

    Script bash:

    #!/usr/bin/env bash
    
    join_by(){ local IFS="$1"; shift; echo "$*"; }
    
    IFS="$1" read -r -a tokens_array <<< "$2"
    IFS=$'\n' sorted=($(sort -n <<<"${tokens_array[*]}"))
    join_by "$1" "${sorted[@]}"
    

    Exemplo:

    $ ./sort_delimited_string.sh "." "192.168.0.1"
    0.1.168.192
    

    Baseado em

    • Dividir string em uma matriz no Bash

    • Como classificar um array no Bash

    • Juntar elementos de um array?

    • 4
  7. jkd
    2018-04-08T03:10:28+08:002018-04-08T03:10:28+08:00

    Aqui alguns bash que adivinham o delimitador por si só:

    #!/bin/bash
    
    delimiter="${1//[[:digit:]]/}"
    if echo $delimiter | grep -q "^\(.\)\1\+$"
    then
      delimiter="${delimiter:0:1}"
      if [[ -z $(echo $1 | grep "^\([0-9]\+"$delimiter"\([0-9]\+\)*\)\+$") ]]
      then
        echo "You seem to have empty fields between the delimiters."
        exit 1
      fi
      if [[ './\' == *$delimiter* ]]
      then
        n=$( echo $1 | sed "s/\\"$delimiter"/\\n/g" | sort -n | tr '\n' ' ' | sed -e "s/\\s/\\"$delimiter"/g")
      else
        n=$( echo $1 | sed "s/"$delimiter"/\\n/g" | sort -n | tr '\n' ' ' | sed -e "s/\\s/"$delimiter"/g")
      fi
      echo ${n%$delimiter}
      exit 0
    else
      echo "The string does not consist of digits separated by one unique delimiter."
      exit 1
    fi
    

    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, sedretornará um erro).

    • 2
  8. agc
    2018-04-09T19:52:15+08:002018-04-09T19:52:15+08:00

    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 reads from standard input, uses POSIX parameter substitution to find the specific delimiter on each line, (stored in $d), and uses tr to replace $d with a newline \n and sorts that line's data, then restores each line's original delimiters:

    sdn() { while read x; do
                d="${x#${x%%[^0-9]*}}"   d="${d%%[0-9]*}"
                x=$(echo -n "$x" | tr "$d" '\n' | sort -g | tr '\n' "$d")
                echo ${x%?}
            done ; }
    

    Applied to the data given in the OP:

    printf "%s\n" "10 50 23 42" "10.1.200.42" "1,100,330,42" "400|500|404" | sdn
    

    Output:

    10 23 42 50
    1.10.42.200
    1,42,100,330
    400|404|500
    
    • 2
  9. Stéphane Chazelas
    2018-04-16T22:15:55+08:002018-04-16T22:15:55+08:00

    For arbitrary delimiters:

    perl -lne '
      @list = /\D+|\d+/g;
      @sorted = sort {$a <=> $b} grep /\d/, @list;
      for (@list) {$_ = shift@sorted if /\d/};
      print @list'
    

    On an input like:

    5,4,2,3
    6|5,2|4
    There are 10 numbers in those 3 lines
    

    It gives:

    2,3,4,5
    2|4,5|6
    There are 3 numbers in those 10 lines
    
    • 2
  10. Kusalananda
    2018-04-08T12:04:30+08:002018-04-08T12:04:30+08:00

    A seguir, uma variação da resposta de Jeff no sentido de gerar um sedscript 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 sedscript é 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 255no loop principal), nem ao número de inteiros. O delimitador pode ser alterado alterando delim='.'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.

    #!/bin/bash
    
    # This function creates a extended regular expression
    # that matches a positive number less than the given parameter.
    lt_pattern() {
        local n="$1"  # Our number.
        local -a res  # Our result, an array of regular expressions that we
                      # later join into a string.
    
        for (( i = 1; i < ${#n}; ++i )); do
            d=$(( ${n: -i:1} - 1 )) # The i:th digit of the number, from right to left, minus one.
    
            if (( d >= 0 )); then
                res+=( "$( printf '%d[0-%d][0-9]{%d}' "${n:0:-i}" "$d" "$(( i - 1 ))" )" )
            fi
        done
    
        d=${n:0:1} # The first digit of the number.
        if (( d > 1 )); then
            res+=( "$( printf '[1-%d][0-9]{%d}' "$(( d - 1 ))" "$(( ${#n} - 1 ))" )" )
        fi
    
        if (( n > 9 )); then
            # The number is 10 or larger.
            res+=( "$( printf '[0-9]{1,%d}' "$(( ${#n} - 1 ))" )" )
        fi
    
        if (( n == 1 )); then
            # The number is 1. The only thing smaller is zero.
            res+=( 0 )
        fi
    
        # Join our res array of expressions into a '|'-delimited string.
        ( IFS='|'; printf '%s\n' "${res[*]}" )
    }
    
    echo ':top'
    
    delim='.'
    
    for (( n = 255; n > 0; --n )); do
        printf 's/\\<%d\\>\\%s\\<(%s)\\>/\\1%s%d/g\n' \
            "$n" "$delim" "$( lt_pattern "$n" )" "$delim" "$n"
    done
    
    echo 'ttop'
    

    O script ficará mais ou menos assim:

    $ bash generator.sh >script.sed
    $ head -n 5 script.sed
    :top
    s/\<255\>\.\<(25[0-4][0-9]{0}|2[0-4][0-9]{1}|[1-1][0-9]{2}|[0-9]{1,2})\>/\1.255/g
    s/\<254\>\.\<(25[0-3][0-9]{0}|2[0-4][0-9]{1}|[1-1][0-9]{2}|[0-9]{1,2})\>/\1.254/g
    s/\<253\>\.\<(25[0-2][0-9]{0}|2[0-4][0-9]{1}|[1-1][0-9]{2}|[0-9]{1,2})\>/\1.253/g
    s/\<252\>\.\<(25[0-1][0-9]{0}|2[0-4][0-9]{1}|[1-1][0-9]{2}|[0-9]{1,2})\>/\1.252/g
    $ tail -n 5 script.sed
    s/\<4\>\.\<([1-3][0-9]{0})\>/\1.4/g
    s/\<3\>\.\<([1-2][0-9]{0})\>/\1.3/g
    s/\<2\>\.\<([1-1][0-9]{0})\>/\1.2/g
    s/\<1\>\.\<(0)\>/\1.1/g
    ttop
    

    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:

    • the ones place
    • the tens place
    • the hundreds place
    • (continued as needed, for larger numbers)
    • or by being smaller in magnitude (number of digits)

    To spell out an example, take 101 (with additional spaces for readability):

    s/ \<101\> \. \<(10[0-0][0-9]{0} | [0-9]{1,2})\> / \1.101 /g
    

    Here, the first alternation allows the numbers 100 through 100; the second alternation allows 0 through 99.

    Another example is 154:

    s/ \<154\> \. \<(15[0-3][0-9]{0} | 1[0-4][0-9]{1} | [0-9]{1,2})\> / \1.154 /g
    

    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:

    for test_run in {1..4}; do
        nums=$(( RANDOM%256 )).$(( RANDOM%256 )).$(( RANDOM%256 )).$(( RANDOM%256 ))
        printf 'nums=%s\n' "$nums"
        sed -E -f script.sed <<<"$nums"
    done
    

    Output:

    nums=90.19.146.232
    19.90.146.232
    nums=8.226.70.154
    8.70.154.226
    nums=1.64.96.143
    1.64.96.143
    nums=67.6.203.56
    6.56.67.203
    
    • 1

relate perguntas

  • A classificação numérica falha ao classificar corretamente o arquivo

  • Multiplicação imprecisa no esquema mit [fechado]

  • Como classificar o número de linhas por data em um arquivo?

  • Listar arquivos classificados de acordo com a linha de conteúdo específica

  • Leia o arquivo de texto com palavras e sua contagem de ocorrências e saída de impressão classificada

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Como exportar uma chave privada GPG e uma chave pública para um arquivo

    • 4 respostas
  • Marko Smith

    ssh Não é possível negociar: "nenhuma cifra correspondente encontrada", está rejeitando o cbc

    • 4 respostas
  • Marko Smith

    Como podemos executar um comando armazenado em uma variável?

    • 5 respostas
  • Marko Smith

    Como configurar o systemd-resolved e o systemd-networkd para usar o servidor DNS local para resolver domínios locais e o servidor DNS remoto para domínios remotos?

    • 3 respostas
  • Marko Smith

    Como descarregar o módulo do kernel 'nvidia-drm'?

    • 13 respostas
  • Marko Smith

    apt-get update error no Kali Linux após a atualização do dist [duplicado]

    • 2 respostas
  • Marko Smith

    Como ver as últimas linhas x do log de serviço systemctl

    • 5 respostas
  • Marko Smith

    Nano - pule para o final do arquivo

    • 8 respostas
  • Marko Smith

    erro grub: você precisa carregar o kernel primeiro

    • 4 respostas
  • Marko Smith

    Como baixar o pacote não instalá-lo com o comando apt-get?

    • 7 respostas
  • Martin Hope
    rocky Como exportar uma chave privada GPG e uma chave pública para um arquivo 2018-11-16 05:36:15 +0800 CST
  • Martin Hope
    Wong Jia Hau ssh-add retorna com: "Erro ao conectar ao agente: nenhum arquivo ou diretório" 2018-08-24 23:28:13 +0800 CST
  • Martin Hope
    Evan Carroll status systemctl mostra: "Estado: degradado" 2018-06-03 18:48:17 +0800 CST
  • Martin Hope
    Tim Como podemos executar um comando armazenado em uma variável? 2018-05-21 04:46:29 +0800 CST
  • Martin Hope
    Ankur S Por que /dev/null é um arquivo? Por que sua função não é implementada como um programa simples? 2018-04-17 07:28:04 +0800 CST
  • Martin Hope
    user3191334 Como ver as últimas linhas x do log de serviço systemctl 2018-02-07 00:14:16 +0800 CST
  • Martin Hope
    Marko Pacak Nano - pule para o final do arquivo 2018-02-01 01:53:03 +0800 CST
  • Martin Hope
    Kidburla Por que verdadeiro e falso são tão grandes? 2018-01-26 12:14:47 +0800 CST
  • Martin Hope
    Christos Baziotis Substitua a string em um arquivo de texto enorme (70 GB), uma linha 2017-12-30 06:58:33 +0800 CST
  • Martin Hope
    Bagas Sanjaya Por que o Linux usa LF como caractere de nova linha? 2017-12-20 05:48:21 +0800 CST

Hot tag

linux bash debian shell-script text-processing ubuntu centos shell awk ssh

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve