예를 들어 Bash에 배열이 있습니다.
array=(a c b f 3 5)
배열을 정렬해야합니다. 내용을 정렬 된 방식으로 표시 할뿐만 아니라 정렬 된 요소를 사용하여 새 배열을 가져옵니다. 새로운 정렬 된 배열은 완전히 새로운 배열이거나 오래된 배열 일 수 있습니다.
답변
실제로 그렇게 많은 코드가 필요하지는 않습니다.
IFS=$'\n' sorted=($(sort <<<"${array[*]}"))
unset IFS
지원 (한이 줄 바꿈이 아니라으로) 요소의 공백, 그리고 배쉬 3.x에서의 작업
예 :
$ array=("a c" b f "3 5")
$ IFS=$'\n' sorted=($(sort <<<"${array[*]}")); unset IFS
$ printf "[%s]\n" "${sorted[@]}"
[3 5]
[a c]
[b]
[f]
참고 : @sorontar이있다 지적 요소와 같은 와일드 카드가 포함 된 경우 치료가 필요하다는 것을 *
나 ?
:
sorted = ($ (…)) 부분은 “split and glob”연산자를 사용하고 있습니다. 당신은 글로브을 끄고해야
set -f
또는set -o noglob
또는shopt -op noglob
또는 같은 배열의 요소*
파일 목록으로 확장됩니다.
무슨 일이야:
결과는 다음 순서로 발생하는 6 가지 결과입니다.
IFS=$'\n'
"${array[*]}"
<<<
sort
sorted=($(...))
unset IFS
먼저 IFS=$'\n'
이는 다음과 같은 방식으로 2와 5의 결과에 영향을 미치는 운영의 중요한 부분입니다.
주어진:
"${array[*]}"
첫 문자로 구분 된 모든 요소로 확장IFS
sorted=()
의 모든 문자를 분할하여 요소를 만듭니다IFS
IFS=$'\n'
새 줄 을 구분 기호로 사용하여 요소를 확장 한 다음 나중에 각 줄이 요소가되는 방식으로 만들어 지도록 설정합니다 . (즉, 새 줄로 나누기)
새로운 라인으로 구분하는 것이 중요합니다. 왜냐하면 그것이 sort
운영 방식 (라인 당 정렬) 이기 때문입니다 . 새 줄 만으로 분할하는 것은 중요하지 않지만 공백이나 탭이 포함 된 요소를 유지해야합니다.
기본값 IFS
은 space , tab , 그 뒤에 새 줄로 표시 되며 작업에 적합하지 않습니다.
다음 sort <<<"${array[*]}"
부분
<<<
여기에서 strings 이라고 "${array[*]}"
하는는 위에서 설명한대로 의 확장을 가져 와서의 표준 입력에 공급합니다 sort
.
이 예에서는 sort
다음 문자열이 제공됩니다.
a c
b
f
3 5
때문에 sort
종류 , 그것은 생산 :
3 5
a c
b
f
다음 sorted=($(...))
부분
$(...)
라는 부분, 명령 치환은 , 그 내용 (원인 sort <<<"${array[*]}
결과하면서, 일반 명령으로 실행)를 표준 출력 지금 어디가는 그 문자 등을 $(...)
했다.
이 예에서는 단순히 쓰기와 비슷한 것을 생성합니다.
sorted=(3 5
a c
b
f
)
sorted
그런 다음 모든 리터럴에서이 리터럴을 분할하여 생성 된 배열이됩니다.
마지막으로 unset IFS
이는의 값을 IFS
기본값으로 재설정하며 좋은 방법입니다.
IFS
나중에 스크립트 에 의존하는 어떤 문제도 발생하지 않도록하기위한 것입니다 . (그렇지 않으면 복잡한 스크립트에는 실용적이지 않은 것들을 바꿔 놓았다는 것을 기억해야합니다.)
답변
원래 답변 :
array=(a c b "f f" 3 5)
readarray -t sorted < <(for a in "${array[@]}"; do echo "$a"; done | sort)
산출:
$ for a in "${sorted[@]}"; do echo "$a"; done
3
5
a
b
c
f f
참고 (특수 문자 나 공백을 포함하는 값이 버전의 대처를 제외하고 줄 바꿈)
참고 readarray는 bash 4 이상에서 지원됩니다.
@Dimitre의 제안에 따라 편집 했습니다.
readarray -t sorted < <(printf '%s\0' "${array[@]}" | sort -z | xargs -0n1)
개행 문자가 올바르게 포함 된 정렬 요소를 이해하는 이점도 있습니다. 불행히도 @ruakh가 올바르게 신호를 보낸 것처럼 이것은 줄 바꿈 문자 로 일반 줄 바꿈 대신 사용할 옵션이 없기 때문에 결과 readarray
가 정확 하다는 의미는 아닙니다 .readarray
NUL
답변
순수한 Bash 퀵 정렬 구현은 다음과 같습니다.
#!/bin/bash
# quicksorts positional arguments
# return is in array qsort_ret
qsort() {
local pivot i smaller=() larger=()
qsort_ret=()
(($#==0)) && return 0
pivot=$1
shift
for i; do
if (( i < pivot )); then
smaller+=( "$i" )
else
larger+=( "$i" )
fi
done
qsort "${smaller[@]}"
smaller=( "${qsort_ret[@]}" )
qsort "${larger[@]}"
larger=( "${qsort_ret[@]}" )
qsort_ret=( "${smaller[@]}" "$pivot" "${larger[@]}" )
}
예를 들어,
$ array=(a c b f 3 5)
$ qsort "${array[@]}"
$ declare -p qsort_ret
declare -a qsort_ret='([0]="3" [1]="5" [2]="a" [3]="b" [4]="c" [5]="f")'
이 구현은 재귀 적입니다. 따라서 반복적 인 퀵 정렬이 있습니다 :
#!/bin/bash
# quicksorts positional arguments
# return is in array qsort_ret
# Note: iterative, NOT recursive! :)
qsort() {
(($#==0)) && return 0
local stack=( 0 $(($#-1)) ) beg end i pivot smaller larger
qsort_ret=("$@")
while ((${#stack[@]})); do
beg=${stack[0]}
end=${stack[1]}
stack=( "${stack[@]:2}" )
smaller=() larger=()
pivot=${qsort_ret[beg]}
for ((i=beg+1;i<=end;++i)); do
if [[ "${qsort_ret[i]}" < "$pivot" ]]; then
smaller+=( "${qsort_ret[i]}" )
else
larger+=( "${qsort_ret[i]}" )
fi
done
qsort_ret=( "${qsort_ret[@]:0:beg}" "${smaller[@]}" "$pivot" "${larger[@]}" "${qsort_ret[@]:end+1}" )
if ((${#smaller[@]}>=2)); then stack+=( "$beg" "$((beg+${#smaller[@]}-1))" ); fi
if ((${#larger[@]}>=2)); then stack+=( "$((end-${#larger[@]}+1))" "$end" ); fi
done
}
두 경우 모두 사용 순서를 변경할 수 있습니다. 문자열 비교를 사용했지만 산술 비교, wrt 파일 수정 시간 비교 등을 수행 할 수 있습니다. 적절한 테스트 만 사용하십시오. 좀 더 일반적인 것으로 만들 수도 있고 테스트 함수가 사용하는 첫 번째 인수를 사용할 수도 있습니다.
#!/bin/bash
# quicksorts positional arguments
# return is in array qsort_ret
# Note: iterative, NOT recursive! :)
# First argument is a function name that takes two arguments and compares them
qsort() {
(($#<=1)) && return 0
local compare_fun=$1
shift
local stack=( 0 $(($#-1)) ) beg end i pivot smaller larger
qsort_ret=("$@")
while ((${#stack[@]})); do
beg=${stack[0]}
end=${stack[1]}
stack=( "${stack[@]:2}" )
smaller=() larger=()
pivot=${qsort_ret[beg]}
for ((i=beg+1;i<=end;++i)); do
if "$compare_fun" "${qsort_ret[i]}" "$pivot"; then
smaller+=( "${qsort_ret[i]}" )
else
larger+=( "${qsort_ret[i]}" )
fi
done
qsort_ret=( "${qsort_ret[@]:0:beg}" "${smaller[@]}" "$pivot" "${larger[@]}" "${qsort_ret[@]:end+1}" )
if ((${#smaller[@]}>=2)); then stack+=( "$beg" "$((beg+${#smaller[@]}-1))" ); fi
if ((${#larger[@]}>=2)); then stack+=( "$((end-${#larger[@]}+1))" "$end" ); fi
done
}
그런 다음이 비교 기능을 가질 수 있습니다.
compare_mtime() { [[ $1 -nt $2 ]]; }
그리고 사용 :
$ qsort compare_mtime *
$ declare -p qsort_ret
현재 폴더의 파일을 수정 시간순으로 정렬합니다 (가장 먼저).
노트. 이 기능은 순수한 배쉬입니다! 외부 유틸리티도없고 서브 쉘도 없습니다! 그것들은 당신이 가질 수있는 재미있는 기호 (공백, 줄 바꿈 문자, 글로브 문자 등)로 안전합니다.
답변
배열 요소에서 특수 쉘 문자를 처리 할 필요가없는 경우 :
array=(a c b f 3 5)
sorted=($(printf '%s\n' "${array[@]}"|sort))
bash 를 사용 하면 외부 정렬 프로그램이 필요합니다.
zsh를 사용하면 외부 프로그램이 필요하지 않으며 특수 쉘 문자를 쉽게 처리 할 수 있습니다.
% array=('a a' c b f 3 5); printf '%s\n' "${(o)array[@]}"
3
5
a a
b
c
f
ksh 는 ASCIIset -s
로 정렬 해야 합니다.
답변
tl; dr :
배열을 정렬 a_in
하고 결과를 저장하십시오 a_out
(요소에는 개행 문자 가 포함되어 있지 않아야 함 [1]
).
배쉬 v4 + :
readarray -t a_out < <(printf '%s\n' "${a_in[@]}" | sort)
배쉬 v3 :
IFS=$'\n' read -d '' -r -a a_out < <(printf '%s\n' "${a_in[@]}" | sort)
antak 솔루션의 장점 :
-
실수로 글 로빙 (배열 요소를 파일 이름 패턴으로 우발적으로 해석)하는 것에 대해 걱정할 필요가 없으므로 글 로빙을 비활성화하기 위해 추가 명령이 필요하지 않습니다 (
set -f
,set +f
나중에 복원). -
로 재설정
IFS
에 대해 걱정할 필요가 없습니다unset IFS
. [2]
읽기 옵션 : 설명 및 샘플 코드
위의 내용은 Bash 코드와 외부 유틸리티 sort
를 결합 하여 임의의 한 줄 요소 및 어휘 또는 숫자 정렬 (선택적 필드 별) 과 함께 작동 하는 솔루션입니다 .
-
성능 : 들어 약 20 원소 이상 ,이 될 것입니다 빠른 순수 배쉬 솔루션보다 – 상당히 점점 그래서 당신은 약 100 요소를 넘어 일단.
(정확한 임계 값은 특정 입력, 기계 및 플랫폼에 따라 다릅니다.)- 빠른 이유 는 Bash 루프를 피하기 때문 입니다.
-
printf '%s\n' "${a_in[@]}" | sort
정렬을 수행합니다 (기본적으로 기본적으로sort
POSIX 사양 참조 ).-
"${a_in[@]}"
안전하게 배열의 요소로 확장a_in
등의 개별 인수 가 (공백 포함)를 포함 무엇이든. -
printf '%s\n'
그런 다음 각 인수, 즉 각 배열 요소를 자체 줄에 그대로 인쇄합니다.
-
-
노트 (A)의 사용 방법 치환 (
<(...)
) 에 대한 입력으로 정렬 된 출력을 제공read
/를readarray
(표준 입력으로 재 통해<
때문에)read
/은readarray
실행해야 현재 쉘 (a 실행 안 하부 쉘 출력 변수의 순서)a_out
가 볼 수 있도록 변수를 스크립트의 나머지 부분에 정의 된 상태로 유지하려면 현재 셸에 추가하십시오. -
sort
의 결과를 배열 변수 로 읽기 :-
Bash v4 + : 각 요소 의 후행 을 포함하지 않고
readarray -t a_out
개별 행 출력sort
을 array variable 요소로 읽습니다 ( ).a_out
\n
-t
-
배시 V3 :
readarray
그래서없는read
사용해야는 :
IFS=$'\n' read -d '' -r -a a_out
지시read
(배열로 읽을-a
) 변수a_out
라인 (통해, 전체 입력을 읽는-d ''
), 그러나 바꿈에 의한 배열 요소로 분할하여 (IFS=$'\n'
.$'\n'
리터럴 개행을 생성하는 (LF를 )는 소위 ANSI C 인용 문자열입니다 ).
(-r
거의 항상와 함께 사용해야하는 옵션read
은 예기치 않은\
문자 처리를 비활성화합니다 .)
-
주석이 달린 샘플 코드 :
#!/usr/bin/env bash
# Define input array `a_in`:
# Note the element with embedded whitespace ('a c')and the element that looks like
# a glob ('*'), chosen to demonstrate that elements with line-internal whitespace
# and glob-like contents are correctly preserved.
a_in=( 'a c' b f 5 '*' 10 )
# Sort and store output in array `a_out`
# Saving back into `a_in` is also an option.
IFS=$'\n' read -d '' -r -a a_out < <(printf '%s\n' "${a_in[@]}" | sort)
# Bash 4.x: use the simpler `readarray -t`:
# readarray -t a_out < <(printf '%s\n' "${a_in[@]}" | sort)
# Print sorted output array, line by line:
printf '%s\n' "${a_out[@]}"
sort
옵션없이 사용 하면 어휘 정렬 (문자 앞의 숫자 정렬 및 숫자 시퀀스가 숫자가 아닌 어휘 적으로 처리됨)이 생성됩니다.
*
10
5
a c
b
f
당신이 숫자 를 원한다면첫 번째 필드를 기준으로 정렬sort -k1,1n
just 대신에를 사용 sort
하면 숫자가 아닌 숫자가 숫자보다 먼저 정렬되고 숫자가 올바르게 정렬됩니다.
*
a c
b
f
5
10
[1] 개행 문자가 포함 된 요소를 처리하려면 다음 변형 ( GNU 와 함께 Bash v4 + sort
)을 사용하십시오.
readarray -d '' -t a_out < <(printf '%s\0' "${a_in[@]}" | sort -z)
.
Michał Górny의 유용한 답변 에는 Bash v3 솔루션이 있습니다.
하면서 2] IFS
된다 배시 V3 변이체 설정, 변경이되는 명령에 범위 .
대조적으로, IFS=$'\n'
antak의 대답에서 따르는 것은 명령이 아닌 할당 이며,이 경우 IFS
변경은 전역 적 입니다.
답변
뮌헨에서 프랑크푸르트까지 3 시간의 기차 여행 (내일 옥토버 페스트가 시작되기 때문에 연락이 어려웠습니다)에서 첫 번째 게시물을 생각하고있었습니다. 전역 정렬을 사용하는 것이 일반적인 정렬 기능에 훨씬 좋습니다. 다음 함수는 임의의 문자열 (줄 바꿈, 공백 등)을 처리합니다.
declare BSORT=()
function bubble_sort()
{ #
# @param [ARGUMENTS]...
#
# Sort all positional arguments and store them in global array BSORT.
# Without arguments sort this array. Return the number of iterations made.
#
# Bubble sorting lets the heaviest element sink to the bottom.
#
(($# > 0)) && BSORT=("$@")
local j=0 ubound=$((${#BSORT[*]} - 1))
while ((ubound > 0))
do
local i=0
while ((i < ubound))
do
if [ "${BSORT[$i]}" \> "${BSORT[$((i + 1))]}" ]
then
local t="${BSORT[$i]}"
BSORT[$i]="${BSORT[$((i + 1))]}"
BSORT[$((i + 1))]="$t"
fi
((++i))
done
((++j))
((--ubound))
done
echo $j
}
bubble_sort a c b 'z y' 3 5
echo ${BSORT[@]}
인쇄합니다 :
3 5 a b c z y
동일한 출력이
BSORT=(a c b 'z y' 3 5)
bubble_sort
echo ${BSORT[@]}
아마도 Bash는 내부적으로 스마트 포인터를 사용하므로 스왑 작업 이 저렴 할 수 있습니다 (의심 할지라도). 그러나 bubble_sort
보다 고급 기능 merge_sort
도 쉘 언어에 도달 할 수 있음을 보여줍니다 .
답변
외부를 사용하는 다른 솔루션 sort
과 및 대처 어떤 (:) NUL을 제외) 특수 문자를. bash-3.2 및 GNU 또는 BSD와 함께 작동해야합니다 sort
(슬프게도 POSIX에는 포함되지 않음 -z
).
local e new_array=()
while IFS= read -r -d '' e; do
new_array+=( "${e}" )
done < <(printf "%s\0" "${array[@]}" | LC_ALL=C sort -z)
먼저 입력 리디렉션을 살펴보십시오. printf
내장 요소를 사용하여 0으로 끝나는 배열 요소를 작성합니다. 인용 부호는 배열 요소가있는 그대로 전달되도록하고 쉘의 특성으로 printf
인해 나머지 각 매개 변수에 대해 형식 문자열의 마지막 부분을 재사용합니다. 즉, 다음과 같습니다.
for e in "${array[@]}"; do
printf "%s\0" "${e}"
done
그런 다음 널 종료 요소 목록이로 전달됩니다 sort
. 그만큼-z
옵션을 사용하면 Null로 끝나는 요소를 읽고 정렬하고 Null로 끝나는 결과를 출력합니다. 고유 한 요소 만 -u
가져와야하는 경우보다 이동성이 뛰어나 므로 전달할 수 있습니다 uniq -z
. 는 LC_ALL=C
로케일 독립적으로 안정적인 정렬 순서를 보장 – 스크립트를 때로는 유용하다. 당신이 원하는 경우 sort
로케일 존중, 그것을 제거합니다.
이 <()
생성자는 생성 된 파이프 라인에서 읽을 설명자를 가져 와서 <
표준 입력을 리디렉션합니다.while
그것을 루프. 파이프 내부의 표준 입력에 액세스해야하는 경우 다른 설명자 (독자 연습)를 사용할 수 있습니다.
이제 처음으로 돌아갑니다. read
내장은 리디렉션 된 표준 입력에서 출력을 읽습니다. 공란으로 설정 IFS
하면 단어 분리가 비활성화되어 여기서 불필요합니다. 결과적 read
으로 입력 된 전체 ‘행’을 단일 제공된 변수로 읽습니다.-r
옵션은 여기서 바람직하지 않은 이스케이프 처리를 비활성화합니다. 마지막으로 -d ''
줄 구분 기호를 NUL로 설정합니다.read
0으로 끝나는 문자열을 읽습니다.
결과적으로 루프는 연속적인 0으로 끝나는 모든 배열 요소에 대해 한 번씩 실행되며 값은 e
. 이 예에서는 항목을 다른 배열에 넣지 만 직접 처리하는 것이 좋습니다. :).
물론 그것은 동일한 목표를 달성하는 많은 방법 중 하나 일뿐입니다. 보시다시피, bash에서 완전한 정렬 알고리즘을 구현하는 것보다 간단하며 경우에 따라 더 빠릅니다. 개행을 포함한 모든 특수 문자를 처리하며 대부분의 일반 시스템에서 작동합니다. 가장 중요한 것은 bash에 대해 새롭고 멋진 것을 가르쳐 줄 수 있습니다. :).