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


139

예를 들어 Bash에 배열이 있습니다.

array=(a c b f 3 5)

배열을 정렬해야합니다. 내용을 정렬 된 방식으로 표시 할뿐만 아니라 정렬 된 요소를 사용하여 새 배열을 가져옵니다. 새로운 정렬 된 배열은 완전히 새로운 배열이거나 오래된 배열 일 수 있습니다.

답변:


208

실제로 그렇게 많은 코드가 필요하지는 않습니다.

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나중에 스크립트 에 의존하는 어떤 문제도 발생하지 않도록하기위한 것입니다 . (그렇지 않으면 복잡한 스크립트에는 실용적이지 않은 것들을 바꿔 놓았다는 것을 기억해야합니다.)


2
@xxor가 없으면 IFS공백이 있으면 요소를 작은 조각으로 나눕니다. 생략 된 IFS=$'\n' 보고보십시오!
antak

3
아주 좋아요 평균적인 bash 사용자 에게이 솔루션의 작동 방식을 설명해 주시겠습니까?
u32004

2
이제와 함께 IFS하나의 특정 종류의 공백이있는 경우 요소를 작은 조각으로 나눕니다. 좋은; not perfect :-)
제한적 속죄

7
unset IFS필요? IFS=명령 앞에 추가 하면 해당 명령의 변경 범위 만 지정하고 나중에 자동으로 이전 값으로 돌아갑니다.
Mark H

10
@MarkH sorted=()명령이 아니라 두 번째 변수 할당 이기 때문에 필요 합니다.
antak

35

원래 답변 :

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


5
참고로, bash 버전 4부터 readarray를 사용할 수 있습니다. 약간 단축 될 수 있습니다 :readarray -t sorted < <(printf '%s\n' "${array[@]}" | sort)
Dimitre Radoulov

1
@Dimitre : 귀하의 제안을 취하고 공백 처리를 내부적으로 nullchar 구분 기호를 사용하여 무엇이든 작동하도록 수정했습니다. 건배
sehe

1
예, sort -z유용한 개선 사항입니다. -z옵션이 GNU 정렬 확장 이라고 가정합니다 .
Dimitre Radoulov

2
포함 된 개행을 처리하려는 경우 고유 한 readarray를 굴릴 수 있습니다. 예를 들면 다음과 같습니다 sorted=(); while read -d $'\0' elem; do sorted[${#sorted[@]}]=$elem; done < <(printf '%s\0' "${array[@]}" | sort -z).. bash v3에서는 readarray를 사용할 수 없으므로 bash v4 대신 bash v3을 사용하는 경우에도 작동합니다.
Bob Bell

1
@ user1527227 프로세스 대체< 와 결합 된 입력 리디렉션 ( ) 입니다. 또는 직관적으로 말하자면 파일이 아니기 때문 입니다. <(...)(printf "bla")
sehe

33

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

현재 폴더의 파일을 수정 시간순으로 정렬합니다 (가장 먼저).

노트. 이 기능은 순수한 배쉬입니다! 외부 유틸리티도없고 서브 쉘도 없습니다! 그것들은 당신이 가질 수있는 재미있는 기호 (공백, 줄 바꿈 문자, 글로브 문자 등)로 안전합니다.


1
입력 요소 및 정렬 기준과 관련하여 뛰어난 유연성을 제공하는 인상적인 Bashing에 대한 조언. 정렬 옵션을 sort제공 하는 라인 기반 정렬 이 충분하면 sort+ read -a솔루션은 20 개 정도의 항목에서 시작하는 것이 더 빠르며 처리하는 요소가 많을수록 점점 더 빨라집니다. 예를 들어, Fusion Drive를 사용하여 OSX 10.11.1을 실행하는 2012 년 말 iMac에서 100 요소 배열 : ca. 0.03 초 ( qsort()) 대 ca. 0.005 초 ( sort+ read -a); 1000 요소 어레이 : ca. 0.375 초 ( qsort()) 대 ca. 0.014 초 ( sort+ read -a).
mklement0

좋은. 나는 대학 시절부터 빠른 정렬을 기억하지만 버블 정렬도 연구 할 것입니다. 내 정렬 요구에 대해 첫 번째와 두 번째 요소를 형성하는 키 다음에 하나의 데이터 요소가 있습니다 (나중에 확장 할 수 있음). 주요 요소 수 (parm1)와 데이터 요소 수 (parm2)로 코드를 개선 할 수 있습니다. OP의 경우 매개 변수는 1과 0입니다. 저의 경우 매개 변수는 2와 1입니다. 어떤 측면에서든 귀하의 대답은 가장 큰 약속입니다.
WinEunuuchs2Unix

1
언 캐스트 문자열 정수의 데이터 세트를 사용 if [ "$i" -lt "$pivot" ]; then하여 확인한 경우 그렇지 않으면 해결 된 "2"< "10"이 true를 리턴했습니다. 나는 이것이 POSIX 대 Lexicographical이라고 믿는다. 또는 인라인 링크 일 수 있습니다.
Page2PagePro

27

배열 요소에서 특수 쉘 문자를 처리 할 필요가없는 경우 :

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 로 정렬 해야 합니다.


아주 좋은 배경 정보. 그 오히려 주제에서 벗어난 것, 그래서 나는 거의 KSH가 설정 -s 플래그를 사용하는 것이 방법에 대한 데모를 요청할 것입니다 ...하지만 다시, 문제는 떠들썩한 파티에
sehe

이는 대부분의 KornShell 구현 (예 : ksh88pdksh )에서 작동합니다. set -A array x 'a a' d; set -s -- "${array[@]}"; set -A sorted "$@" 물론 set 명령은 현재 위치 매개 변수가있는 경우 재설정합니다.
Dimitre Radoulov

당신은 진정한 지식의 샘입니다. 나는 미묘한 차이가 이런 종류의 인간 종의 다른 구성원의 대부분을 회피 때문에 정보의 전체 패키지, :), +1을 photographics 메모리 또는 뭔가가 있어야 확신
sehe

10

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변경은 전역 적 입니다.


8

뮌헨에서 프랑크푸르트까지 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도 쉘 언어에 도달 할 수 있음을 보여줍니다 .


5
버블 정렬? 와우. 오바마는 "거품 정렬은 잘못된 길로 갈 것"이라고 말합니다-> youtube.com/watch?v=k4RRi_ntQc8
Robottinosino

1
글쎄, O-guy는 똑똑해지기를 원했지만 이것이 50/50의 기회 질문이 아니라는 것을 느끼지 못했던 것 같습니다. O-guy의 전임자였던 B-guy에게 한 번 더 나아 졌다고 말해 보자. 질문에 대답 할 수 없습니다. " 이 B-guy는 부울 논리에 대해 정말 알고 있습니다. O- 남자는 그렇지 않습니다.
Andreas Spindler

정렬 할 배열에 대한 이름 참조가있는 로컬 배열을 BSORT로 만들어이 기능을보다 쉽게 ​​이식 할 수 있습니다. 즉 local -n BSORT="$1"기능 시작시. 그런 다음 myarraybubble_sort myarray 를 정렬하기 위해 실행할 수 있습니다 .
johnraff

7

외부를 사용하는 다른 솔루션 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에 대해 새롭고 멋진 것을 가르쳐 줄 수 있습니다. :).


훌륭한 솔루션과 매우 유용한 설명, 감사합니다. 하나의 확장 : IFS를 비워 두지 않으면 단어 분리가 없어도 선행 공백도 제거됩니다.
Dirk Herrmann

로컬 변수를 도입 e하고 빈 IFS를 설정 하는 대신 REPLY 변수를 사용하십시오.
Robin A. Meade

2

이 시도:

echo ${array[@]} | awk 'BEGIN{RS=" ";} {print $1}' | sort

출력은 다음과 같습니다.

삼
5
ㅏ
비
씨
에프

문제 해결됨.


3
그의 질문에 완전히 대답하기 위해 출력을 새로운 배열에 넣으려면 이것을 편집해야합니다.
피터 오람

2

다음과 같이 배열의 각 요소에 대해 고유 한 정수를 계산할 수있는 경우 :

tab='0123456789abcdefghijklmnopqrstuvwxyz'

# build the reversed ordinal map
for ((i = 0; i < ${#tab}; i++)); do
    declare -g ord_${tab:i:1}=$i
done

function sexy_int() {
    local sum=0
    local i ch ref
    for ((i = 0; i < ${#1}; i++)); do
        ch="${1:i:1}"
        ref="ord_$ch"
        (( sum += ${!ref} ))
    done
    return $sum
}

sexy_int hello
echo "hello -> $?"
sexy_int world
echo "world -> $?"

Bash는 항상 희소 배열을 사용하므로 사용하지 않는 인덱스에 대해 걱정할 필요가 없으므로 이러한 정수를 배열 인덱스로 사용할 수 있습니다.

array=(a c b f 3 5)
for el in "${array[@]}"; do
    sexy_int "$el"
    sorted[$?]="$el"
done

echo "${sorted[@]}"
  • 찬성 빠른.
  • 단점 중복 된 요소가 병합되어 내용을 32 비트 고유 정수로 매핑하는 것이 불가능할 수 있습니다.

흥미로운 기술, 나는 명시 적 비교 / 정렬없이 최대 / 최소 값을 찾기 위해 변형을 사용했습니다. 그러나 길이에 관계없이 가중되지 않은 덧셈은 작동하지 않습니다. "z"는 "aaaa"보다 먼저 정렬되므로 위에 표시된대로 단어에 사용할 수 없습니다.
mr.spuratic

2

최소 정렬 :

#!/bin/bash
array=(.....)
index_of_element1=0

while (( ${index_of_element1} < ${#array[@]} )); do

    element_1="${array[${index_of_element1}]}"

    index_of_element2=$((index_of_element1 + 1))
    index_of_min=${index_of_element1}

    min_element="${element_1}"

        for element_2 in "${array[@]:$((index_of_element1 + 1))}"; do
            min_element="`printf "%s\n%s" "${min_element}" "${element_2}" | sort | head -n+1`"      
            if [[ "${min_element}" == "${element_2}" ]]; then
                index_of_min=${index_of_element2}
            fi
            let index_of_element2++
        done

        array[${index_of_element1}]="${min_element}"
        array[${index_of_min}]="${element_1}"

    let index_of_element1++
done

1
array=(a c b f 3 5)
new_array=($(echo "${array[@]}" | sed 's/ /\n/g' | sort))    
echo ${new_array[@]}

new_array의 에코 내용은 다음과 같습니다.

3 5 a b c f

1

공백과 줄 바꾸기의 일반적인 문제에 대한 해결 방법이 있습니다.

원래의 배열 (같은 아닌 문자를 사용 $'\1'하거나 $'\4'또는 유사한).

이 함수는 작업을 수행합니다.

# Sort an Array may have spaces or newlines with a workaround (wa=$'\4')
sortarray(){ local wa=$'\4' IFS=''
             if [[ $* =~ [$wa] ]]; then
                 echo "$0: error: array contains the workaround char" >&2
                 exit 1
             fi

             set -f; local IFS=$'\n' x nl=$'\n'
             set -- $(printf '%s\n' "${@//$nl/$wa}" | sort -n)
             for    x
             do     sorted+=("${x//$wa/$nl}")
             done
       }

이것은 배열을 정렬합니다 :

$ array=( a b 'c d' $'e\nf' $'g\1h')
$ sortarray "${array[@]}"
$ printf '<%s>\n' "${sorted[@]}"
<a>
<b>
<c d>
<e
f>
<gh>

소스 배열에 해결 방법 문자가 포함되어 있다고 불평합니다.

$ array=( a b 'c d' $'e\nf' $'g\4h')
$ sortarray "${array[@]}"
./script: error: array contains the workaround char

기술

  • 두 개의 로컬 변수 wa(해결 방법 char)와 null IFS를 설정했습니다.
  • 그런 다음 (if null이있는 경우) 전체 배열을 테스트합니다 $*.
  • woraround char이 포함되어 있지 않습니다 [[ $* =~ [$wa] ]].
  • 그렇다면 메시지를 표시하고 오류를 표시하십시오. exit 1
  • 파일 이름 확장을 피하십시오. set -f
  • IFS=$'\n'루프 변수 x와 개행 변수 var ( nl=$'\n') 를 IFS ( ) 의 새 값으로 설정하십시오 .
  • 수신 된 인수 (입력 배열 $@) 의 모든 값을 인쇄합니다 .
  • 그러나 해결 방법 char으로 새 줄을 바꿉니다 "${@//$nl/$wa}".
  • 정렬 할 값을 보냅니다 sort -n.
  • 정렬 된 모든 값을 위치 인수에 다시 넣습니다 set --.
  • 그런 다음 각 인수를 하나씩 지정하여 줄 바꿈을 유지합니다.
  • 루프에서 for x
  • 새로운 배열로 : sorted+=(…)
  • 기존의 개행을 유지하기 위해 따옴표 안에 넣습니다.
  • 해결 방법을 개행으로 복원합니다 "${x//$wa/$nl}".
  • 끝난

1

이 질문 은 밀접한 관련이 있습니다. 그리고 BTW, 다음은 Bash의 병합 소트입니다 (외부 프로세스 제외).

mergesort() {
  local -n -r input_reference="$1"
  local -n output_reference="$2"
  local -r -i size="${#input_reference[@]}"
  local merge previous
  local -a -i runs indices
  local -i index previous_idx merged_idx \
           run_a_idx run_a_stop \
           run_b_idx run_b_stop

  output_reference=("${input_reference[@]}")
  if ((size == 0)); then return; fi

  previous="${output_reference[0]}"
  runs=(0)
  for ((index = 0;;)) do
    for ((++index;; ++index)); do
      if ((index >= size)); then break 2; fi
      if [[ "${output_reference[index]}" < "$previous" ]]; then break; fi
      previous="${output_reference[index]}"
    done
    previous="${output_reference[index]}"
    runs+=(index)
  done
  runs+=(size)

  while (("${#runs[@]}" > 2)); do
    indices=("${!runs[@]}")
    merge=("${output_reference[@]}")
    for ((index = 0; index < "${#indices[@]}" - 2; index += 2)); do
      merged_idx=runs[indices[index]]
      run_a_idx=merged_idx
      previous_idx=indices[$((index + 1))]
      run_a_stop=runs[previous_idx]
      run_b_idx=runs[previous_idx]
      run_b_stop=runs[indices[$((index + 2))]]
      unset runs[previous_idx]
      while ((run_a_idx < run_a_stop && run_b_idx < run_b_stop)); do
        if [[ "${merge[run_a_idx]}" < "${merge[run_b_idx]}" ]]; then
          output_reference[merged_idx++]="${merge[run_a_idx++]}"
        else
          output_reference[merged_idx++]="${merge[run_b_idx++]}"
        fi
      done
      while ((run_a_idx < run_a_stop)); do
        output_reference[merged_idx++]="${merge[run_a_idx++]}"
      done
      while ((run_b_idx < run_b_stop)); do
        output_reference[merged_idx++]="${merge[run_b_idx++]}"
      done
    done
  done
}

declare -ar input=({z..a}{z..a})
declare -a output

mergesort input output

echo "${input[@]}"
echo "${output[@]}"

0

Bash에서 외부 정렬 프로그램이 필요하다고 확신하지 않습니다.

다음은 간단한 버블 정렬 알고리즘에 대한 구현입니다.

function bubble_sort()
{   #
    # Sorts all positional arguments and echoes them back.
    #
    # Bubble sorting lets the heaviest (longest) element sink to the bottom.
    #
    local array=($@) max=$(($# - 1))
    while ((max > 0))
    do
        local i=0
        while ((i < max))
        do
            if [ ${array[$i]} \> ${array[$((i + 1))]} ]
            then
                local t=${array[$i]}
                array[$i]=${array[$((i + 1))]}
                array[$((i + 1))]=$t
            fi
            ((i += 1))
        done
        ((max -= 1))
    done
    echo ${array[@]}
}

array=(a c b f 3 5)
echo " input: ${array[@]}"
echo "output: $(bubble_sort ${array[@]})"

이것은 다음을 인쇄합니다 :

 input: a c b f 3 5
output: 3 5 a b c f

버블 정렬은 O(n^2)입니다. 나는 대부분의 정렬 알고리즘 O(n lg(n))이 마지막 12 개 정도의 요소까지 사용한다는 것을 기억 합니다. 최종 요소에는 선택 정렬이 사용됩니다.
jww


-1

sorted=($(echo ${array[@]} | tr " " "\n" | sort))

bash / linux의 정신으로 각 단계마다 최상의 명령 줄 도구를 파이프합니다. sort주요 작업을 수행하지만 공간 대신 ​​줄 바꿈으로 구분 된 입력이 필요하므로 위의 매우 간단한 파이프 라인은 간단합니다.

에코 배열 내용-> 줄 바꾸기로 공간 바꾸기-> 정렬

$() 결과를 에코하는 것입니다

($()) "반향 된 결과"를 배열에 넣는 것입니다

참고 : @sorontar 는 다른 질문 에 대한 의견 에서 언급했습니다 .

sorted = ($ (...)) 부분은 "split and glob"연산자를 사용하고 있습니다. glob를 해제해야합니다. set -f 또는 set -o noglob 또는 shopt -op noglob 또는 *와 같은 배열 요소는 파일 목록으로 확장됩니다.


bash / linux 의 정신에서 : 나는 당신이 정신을 전혀 이해하지 못했다고 생각합니다. 코드가 완전히 손상되었습니다 (경로 확장 및 단어 분리). 이것은 더 좋습니다 (Bash≥4) : mapfile -t sorted < <(printf '%s\n' "${array[@]}" | sort), 그렇지 않으면 sorted=(); while IFS= read -r line; do sorted+=( "$line" ); done < <(printf '%s\n' | sort).
gniourf_gniourf 19

사용하는 반 패턴은 다음 echo ${array[@]} | tr " " "\n"과 같습니다 . : 배열 필드에 공백과 glob 문자가 포함되어 있으면 중단됩니다. 게다가, 그것은 서브 쉘을 생성하고 쓸모없는 외부 명령을 사용합니다. 그리고 echo바보이기 때문에 배열이 -e, -E또는로 시작하면 중단됩니다 -n. 대신 다음을 사용하십시오 printf '%s\n' "${array[@]}".. 다른 반 패턴은 : ($())"반향 된 결과"를 배열에 넣는 것 입니다. 확실히! 이것은 경로 이름 확장 (글 로빙) 및 단어 분리로 인해 끔찍한 반 패턴입니다. 이 공포를 사용하지 마십시오.
gniourf_gniourf

정답은 "끔찍한 반 패턴"입니다. 그리고 자신이 대답 한 질문에 대해 다른 사람의 대답을 내리는 방법.
마이클
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.