한 줄의 구분 된 항목을 숫자로 정렬하려면 어떻게해야합니까?


11

임의의 문자로 구분 된 숫자의 줄 (또는 많은 줄)이 있습니다. 구분 기호를 유지하면서 각 줄의 항목을 숫자로 정렬하는 데 어떤 UNIX 도구를 사용할 수 있습니까?

예를 들면 다음과 같습니다.

  • 숫자 목록; 입력 : 10 50 23 42; 정렬 :10 23 42 50
  • IP 주소; 입력 : 10.1.200.42; 정렬 :1.10.42.200
  • CSV; 입력 : 1,100,330,42; 정렬 :1,42,100,330
  • 파이프로 구분; 입력 : 400|500|404; 정렬 :400|404|500

구분 기호는 임의적이므로 원하는 단일 문자 구분 기호를 사용하여 정답을 제공하거나 확장하십시오.


8
당신은 :) codegolf에 걸쳐 게시한다
ivanivan


옵션으로 cut임의의 구분자 를 지원 하는 힌트입니다 -d.
Oleg Lobachev

이 4 가지 DSV 예제가 동일한 파일에 있는지 또는 4 개의 다른 파일의 샘플인지에 대해 설명하십시오.
agc

2
다른 의견 중 일부를 보면 구분 기호는 임의적이지만 입력에서 일관되게 사용됩니다. 데이터 생산자 측에서 쉼표를 구분 기호로 사용하지 않고 데이터에서 사용하지 않도록 지능을 가정하십시오 (예 : 4,325 comma 55 comma 42,430발생하지 않음 1.5 period 4.2).
Jeff Schaller

답변:


12

당신은 이것을 달성 할 수 있습니다 :

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

. 을 구분 기호로 바꾸 십시오. 중복을 제거하려면 위 의 명령에
추가 하십시오.-usort


또는 gawk( GNU awk )를 사용하면 많은 줄을 처리 할 수 ​​있으며 위의 내용도 확장 할 수 있습니다.

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

대신 *에 필드 구분자로 SEP='*'당신과 구분 .


참고 : 모든 숫자 클래스 (정수, 부동 수, 과학 수, 16 진수 등)를 처리하는 대신 옵션
을 사용해야 할 수도 있습니다 .-g, --general-numeric-sortsort-n, --numeric-sort

$ 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

에서 awk필요없이 변화, 그것은 여전히 그 처리됩니다.


10

사용 perl명백한 버전이있다; 데이터를 분할하고 정렬 한 후 다시 연결하십시오.

구분 기호는 두 번 나열되어야합니다 (에서 한 번 split및에서 한 번 join)

예를 들어 ,

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

그래서

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

(가)부터 split정규식이며, 문자는 인용해야 할 수도 있습니다 :

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

-a-F옵션 을 사용하면 분할을 제거 할 수 있습니다. -p이전과 같이 루프를 사용하여 결과를로 설정 $_하면 자동으로 인쇄됩니다.

perl -F'/\./' -aple '$_=join(".", sort {$a <=> $b} @F)'

4
-l대신에 옵션을 사용할 수 있습니다 chomp. 또한 인쇄시 줄 바꿈이 다시 추가됩니다. 분할 부분에 대해서는 -a(로 -F) 도 참조하십시오 .
Stéphane Chazelas

1
-l-F:, 심지어 좋네요perl -F'/\./' -le 'print join(".", sort {$a <=> $b} @F)'
muru는

@ StéphaneChazelas -l옵션에 감사드립니다 . 나는 그것을 놓쳤다!
Stephen Harris

1
@muru 나는 -F모든 버전에서 제대로 작동하지 않기 때문에 원래 플래그를 사용 하지 않았습니다 (예 : CentOS 7-perl 5.16.3의 라인은 빈 출력을 반환하지만 데비안 9에서는 제대로 작동하지만). 그러나 -p그것 과 결합 하면 결과가 약간 작아 지므로 대답의 대안으로 추가했습니다. -F사용 방법 을 보여줍니다 . 감사!
Stephen Harris

2
펄의 최신 버전이 자동으로 추가하기 때문에입니다 @StephenHarris -a-n때 옵션 -F사용되며 -n경우에 -a사용됩니다 ... 그래서 그냥 변경 -le-lane
Sundeep

4

Stephen Harris의 답변 과 비슷한 Python 및 유사한 아이디어 사용 :

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>

그래서 같은 :

$ 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

안타깝게도 I / O를 수동으로 수행해야하는 것은 Perl 버전보다 훨씬 덜 우아합니다.



3

껍질

더 높은 수준의 언어를로드하려면 시간이 걸립니다.
몇 줄은 쉘 자체가 해결책 일 수 있습니다.
외부 명령 sort과 명령을 사용할 수 있습니다 tr. 하나는 줄을 정렬하는 데 매우 효율적이고 다른 하나는 한 구분 기호를 개행 문자로 변환하는 데 효과적입니다.

#!/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'

이것은 <<<오직 사용하기 때문에 bash가 필요 합니다. 이것이 here-doc로 대체되면 솔루션은 posix에 유효합니다.
이 탭, 공백이나 쉘 글로브 문자로 필드를 정렬 할 수있다 ( *, ?, [). 각 줄이 정렬되므로 줄 바꿈이 아닙니다.

파일 이름을 처리 <<<"$2"하도록 변경 하고 <"$2"다음과 같이 호출하십시오.

shsort '.'    infile

구분 기호는 전체 파일에서 동일합니다. 이것이 제한이라면 개선 될 수 있습니다.

그러나 6000 줄만있는 파일을 처리하는 데 15 초가 걸립니다. 실제로 쉘은 파일을 처리하는 데 가장 적합한 도구는 아닙니다.

어 wk

몇 줄 이상 (10 개 이상)에는 실제 프로그래밍 언어를 사용하는 것이 좋습니다. awk 해결책은 다음과 같습니다.

#!/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

위에서 언급 한 동일한 6000 줄 파일에 대해 0.2 초 밖에 걸리지 않습니다.

<"$2"for 파일은 <<<"$2"쉘 변수 내부의 행 으로 다시 변경 될 수 있음을 이해 하십시오.

가장 빠른 해결책은 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'

파일 변경 <<<"$a"을 간단하게 정렬하고 perl 옵션에 "$a"추가 -i하여 파일 개정판을 "제자리에"배치하려면 다음을 수행하십시오.

#!/bin/bash
perlsort(){  perl -lpi -e '$_=join("'"$1"'",sort {$a <=> $b} split(/['"$1"']/))' "$2"; }

perlsort '.' infile; exit

2

sedIP 주소의 옥텟을 정렬하는 데 사용

sed내장 sort함수가 없지만 데이터가 범위 (예 : IP 주소)로 충분히 제한되어 있으면 간단한 버블 정렬 을 수동으로 구현하는 sed 스크립트를 생성 할 수 있습니다 . 기본 메커니즘은 순서가 잘못된 인접 숫자를 찾는 것입니다. 번호가 잘못된 경우 교체하십시오.

sed옥텟의 처음 두 쌍 중 하나 (후단 분리 강제 세번째 옥텟의 끝을 표시하기 위해 존재하는) 및 : 스크립트 자체는 두 개의 검색 및 스왑 아웃 - 오브 - 오더 번호의 각 쌍에 대한 명령을 포함 3 번째 옥텟 쌍 (EOL로 끝남)에 대해 두 번째. 스왑이 발생하면 프로그램은 스크립트의 맨 위로 분기되어 순서가 잘못된 숫자를 찾습니다. 그렇지 않으면 종료됩니다.

생성 된 스크립트는 다음과 같습니다.

$ 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

이 접근법은 마침표를 구분 기호로 하드 코딩합니다. 그렇지 않으면 이스케이프해야합니다. 그렇지 않으면 정규 표현식 구문에 "특별"하므로 문자를 허용합니다.

이러한 sed 스크립트를 생성하기 위해이 루프는 다음을 수행합니다.

#!/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'

해당 스크립트의 출력을 다른 파일로 리디렉션하십시오 (예 :) sort-ips.sed.

그런 다음 샘플 실행은 다음과 같습니다.

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

를 생성하는 스크립트에 다음과 같은 변화는 단어 경계 마커를 사용 \<하고 \>두 번째 대체의 필요성을 제거 할 수 있습니다. 또한 생성 된 스크립트의 크기를 1.3MB에서 900KB 바로 아래로 줄이며 sed자체 실행 시간을 크게 줄 입니다 ( sed사용중인 구현 에 따라 원본의 약 50 % -75 % ).

#!/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'

1
흥미로운 생각이지만, 일을 조금 복잡하게 만드는 것 같습니다.
Matt

1
@Matt 그것은 좀 요점입니다. 무엇이든 정렬하는 sed것은 어리석기 때문에 흥미로운 도전입니다.
Kusalananda

2

여기에 구분 기호 자체를 추측하는 bash가 있습니다.

#!/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

매우 효율적이거나 깨끗하지는 않지만 작동합니다.

처럼 사용하십시오 bash my_script.sh "00/00/18/29838/2".

동일한 분리 문자가 일관되게 사용되지 않거나 둘 이상의 분리 문자가 서로 뒤따를 때 오류를 리턴합니다.

사용 된 분리 문자가 특수 문자 인 경우 이스케이프 처리됩니다 (그렇지 않으면 sed오류를 리턴 함).


그 영감 .
agc

2

이 답변은 Q.에 대한 오해를 바탕으로하지만 어떤 경우에는 정확합니다. 입력이 전적으로 자연수 이고 줄에 하나의 구분 기호 만 있는 경우 (Q의 샘플 데이터에서와 같이) 올바르게 작동합니다. 또한 각각 고유 한 구분 기호가있는 줄이있는 파일도 처리합니다.

이 쉘 기능 read표준 입력들, 사용 POSIX 파라미터를 교체 (에 저장된 각 라인에 특정 구분자를 찾기 위해 $d), 및 용도 tr대체 $d개행으로 \n하고 sort각 행의 원래 분리를 복원하는 행의 데이터를 S :

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 ; }

OP에 주어진 데이터에 적용 :

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

산출:

10 23 42 50
1.10.42.200
1,42,100,330
400|404|500

모든 줄의 구분 기호는 일관됩니다. 사용자가 구분 기호를 선언 할 수있는 일반적인 솔루션은 훌륭하지만 대답은 그에 맞는 구분 기호 (숫자 데이터 자체에는없는 문자)를 가정 할 수 있습니다.
Jeff Schaller

2

임의의 구분 기호의 경우 :

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

다음과 같은 입력에서 :

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

그것은 제공합니다 :

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

0

숫자가 아닌 (0-9) 구분 기호를 처리해야합니다. 예:

x='1!4!3!5!2'; delim=$(echo "$x" | tr -d 0-9 | cut -b1); echo "$x" | tr "$delim" '\n' | sort -g | tr '\n' "$delim" | sed "s/$delim$/\n/"

산출:

1!2!3!4!5

0

perl:

$ # -a to auto-split on whitespace, results in @F array
$ echo 'foo baz v22 aimed' | perl -lane 'print join " ", sort @F'
aimed baz foo v22
$ # {$a <=> $b} for numeric comparison, {$b <=> $a} will give descending order
$ echo '1,100,330,42' | perl -F, -lane 'print join ",", sort {$a <=> $b} @F'
1,42,100,330

ruby다소 유사하다,perl

$ # -a to auto-split on whitespace, results in $F array
$ # $F is sorted and then joined using the given string
$ echo 'foo baz v22 aimed' | ruby -lane 'print $F.sort * " "'
aimed baz foo v22

$ # (&:to_i) to convert string to integer
$ echo '1,100,330,42' | ruby -F, -lane 'print $F.sort_by(&:to_i) * ","'
1,42,100,330

$ echo '10.1.200.42' | ruby -F'\.' -lane 'print $F.sort_by(&:to_i) * "."'
1.10.42.200


사용자 정의 명령 및 구분 기호 문자열 (정규 표현식 아님) 만 전달하십시오. 입력에 부동 데이터가있는 경우 작동합니다

$ # by default join uses value of $,
$ sort_line(){ ruby -lne '$,=ENV["d"]; print $_.split($,).sort_by(&:to_f).join' ; }

$ s='103,14.5,30,24'
$ echo "$s" | d=',' sort_line
14.5,24,30,103
$ s='10.1.200.42'
$ echo "$s" | d='.' sort_line
1.10.42.200

$ # for file input
$ echo '123--87--23' > ip.txt
$ echo '3--12--435--8' >> ip.txt
$ d='--' sort_line <ip.txt
23--87--123
3--8--12--435


에 대한 사용자 정의 명령 perl

$ sort_line(){ perl -lne '$d=$ENV{d}; print join $d, sort {$a <=> $b} split /\Q$d/' ; }
$ s='123^[]$87^[]$23'
$ echo "$s" | d='^[]$' sort_line 
23^[]$87^[]$123


추가 독서-나는 이미 편리한 펄 / 루비 원 라이너 목록을 가지고있다.


0

다음은 Bubble 정렬을 수행 하는 스크립트를 생성한다는 점에서 Jeff의 답변 에 대한 변형 sed이지만 자체 답변을 보증하기에는 충분히 다릅니다.

차이점은 O (n ^ 2) 기본 정규 표현식을 생성하는 대신 O (n) 확장 정규 표현식을 생성한다는 것입니다. 결과 스크립트는 약 15KB입니다. sed스크립트 실행 시간은 1 초 단위입니다 (스크립트 생성에 시간이 조금 더 걸립니다).

점으로 구분 된 양의 정수를 정렬하는 것으로 제한되지만 정수의 크기 ( 255메인 루프에서 증가 ) 또는 정수의 수로 제한되지 않습니다 . 구분 기호는 delim='.'코드를 변경하여 변경할 수 있습니다 .

정규 표현식을 올바르게 얻기 위해 머리를 숙 였으므로 다른 날의 세부 사항을 설명하겠습니다.

#!/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'

스크립트는 다음과 같습니다.

$ 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

생성 된 정규 표현식의 기본 개념은 각 정수보다 작은 숫자에 대해 패턴 일치를하는 것입니다. 이 두 숫자는 순서가 잘못되어 교체됩니다. 정규식은 여러 OR 옵션으로 그룹화됩니다. 각 항목에 추가 된 범위에주의를 기울이십시오 (때로는 {0}). 즉, 바로 이전 항목은 검색에서 생략됩니다. 왼쪽에서 오른쪽으로 정규식 옵션은 다음과 같이 주어진 숫자보다 작은 숫자를 찾습니다.

  • 그 장소
  • 열 자리
  • 수백 곳
  • (더 큰 숫자를 위해 필요에 따라 계속)
  • 또는 크기가 작을수록 (자릿수)

예제를 작성하려면 101가독성을 위해 추가 공백을 넣습니다.

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

여기서, 제 1 교번은 100 내지 100의 숫자를 허용하고; 두 번째 교번은 0에서 99까지 허용됩니다.

다른 예는 154다음과 같습니다.

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

여기서 첫 번째 옵션은 150에서 153까지 허용합니다. 두 번째는 100에서 149까지, 마지막은 0에서 99까지 허용합니다.

루프에서 네 번 테스트 :

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

산출:

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

-2

입력을 여러 줄로 나누기

을 사용 tr하면 임의의 구분 기호를 사용하여 입력을 여러 줄로 나눌 수 있습니다.

그런 다음이 입력을 통과 할 수 있습니다 sort( -n입력이 숫자 인 경우 사용 ).

출력에 분리 문자를 유지하려면 분리 문자를 tr다시 추가 하기 위해 다시 사용할 수 있습니다 .

예를 들어 공백을 구분자로 사용

cat input.txt | tr " " "\n" | sort -n | tr "\n" " "

입력 : 1 2 4 1 4 32 18 3 출력 :1 1 2 3 4 4 18 32


숫자 항목을 안전하게 가정 할 수 있으며 예 : 구분 기호를 교체해야합니다.
Jeff Schaller
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.