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

임의의 문자로 구분 된 숫자의 줄 (또는 많은 줄)이 있습니다. 구분 기호를 유지하면서 각 줄의 항목을 숫자로 정렬하는 데 어떤 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

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



답변

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

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필요없이 변화, 그것은 여전히 그 처리됩니다.


답변

사용 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)'


답변

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 버전보다 훨씬 덜 우아합니다.


답변

배쉬 스크립트 :

#!/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[@]}"

예:

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

에 기초


답변

껍질

더 높은 수준의 언어를로드하려면 시간이 걸립니다.
몇 줄은 쉘 자체가 해결책 일 수 있습니다.
외부 명령 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


답변

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'


답변

여기에 구분 기호 자체를 추측하는 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오류를 리턴 함).