임의의 문자로 구분 된 숫자의 줄 (또는 많은 줄)이 있습니다. 구분 기호를 유지하면서 각 줄의 항목을 숫자로 정렬하는 데 어떤 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'.' -
점 .
을 구분 기호로 바꾸 십시오. 중복을 제거하려면 위 의 명령에
추가 하십시오.-u
sort
또는 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-sort
sort
-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
답변
sed
IP 주소의 옥텟을 정렬하는 데 사용
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
오류를 리턴 함).