[arrays] Bash에서 배열을 정렬하는 방법

예를 들어 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 가지 결과입니다.

  1. IFS=$'\n'
  2. "${array[*]}"
  3. <<<
  4. sort
  5. sorted=($(...))
  6. unset IFS

먼저 IFS=$'\n'

이는 다음과 같은 방식으로 2와 5의 결과에 영향을 미치는 운영의 중요한 부분입니다.

주어진:

  • "${array[*]}" 첫 문자로 구분 된 모든 요소로 확장 IFS
  • sorted=() 의 모든 문자를 분할하여 요소를 만듭니다 IFS

IFS=$'\n' 새 줄 을 구분 기호로 사용하여 요소를 확장 한 다음 나중에 각 줄이 요소가되는 방식으로 만들어 지도록 설정합니다 . (즉, 새 줄로 나누기)

새로운 라인으로 구분하는 것이 중요합니다. 왜냐하면 그것이 sort운영 방식 (라인 당 정렬) 이기 때문입니다 . 새 줄 만으로 분할하는 것은 중요하지 않지만 공백이나 탭이 포함 된 요소를 유지해야합니다.

기본값 IFSspace , 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정확 하다는 의미는 아닙니다 .readarrayNUL


답변

순수한 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

kshASCIIset -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 정렬을 수행합니다 (기본적으로 기본적으로 sortPOSIX 사양 참조 ).

    • "${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에 대해 새롭고 멋진 것을 가르쳐 줄 수 있습니다. :).