programing

Bash에서 문자열을 배열로 분할하는 방법

codeshow 2023. 4. 12. 22:43
반응형

Bash에서 문자열을 배열로 분할하는 방법

Bash 스크립트에서는 행을 분할하여 배열에 저장하고 싶습니다.

예를 들어, 다음 행이 지정됩니다.

Paris, France, Europe

결과 어레이를 다음과 같이 만들고 싶습니다.

array[0] = Paris
array[1] = France
array[2] = Europe

간단한 구현이 바람직합니다.속도는 중요하지 않습니다.어떻게 해야 하죠?

IFS=', ' read -r -a array <<< "$string"

「 」의는 「 」의 문자인 것에 해 주세요.$IFS는 구분자로 개별적으로 취급되기 때문에 이 경우 필드는 두 문자의 순서가 아닌 쉼표 또는 공백으로 구분됩니다.흥미로운 점은 입력에 쉼표 공간이 표시될 때 공백 필드가 생성되지 않는다는 것입니다. 공백은 특별히 처리되기 때문입니다.

개별 요소에 액세스하려면:

echo "${array[0]}"

요소를 반복하려면:

for element in "${array[@]}"
do
    echo "$element"
done

인덱스와 값을 모두 가져오려면:

for index in "${!array[@]}"
do
    echo "$index ${array[index]}"
done

마지막 예는 Bash 배열이 희박하기 때문에 유용합니다.즉, 요소를 삭제하거나 요소를 추가하면 인덱스가 연속되지 않습니다.

unset "array[1]"
array[42]=Earth

배열 내의 요소 수를 가져오려면:

echo "${#array[@]}"

위에서 설명한 바와 같이 배열은 희박할 수 있으므로 마지막 요소를 가져오는 데 길이를 사용하지 마십시오.Bash 4.2 이후에서는 다음과 같이 할 수 있습니다.

echo "${array[-1]}"

(2.05b 이후 어딘가에서):

echo "${array[@]: -1:1}"

음의 오프셋이 클수록 배열 끝에서 더 멀리 선택됩니다.이전 형식의 빼기 기호 앞에 공백이 있습니다.필수입니다.

이 질문에 대한 모든 답은 어떤 식으로든 틀렸다.


오답 1번

IFS=', ' read -r -a array <<< "$string"

1: 이것은 의 오용입니다.$IFS의값$IFS변수는 단일 변수 길이 문자열 구분자로 간주되지 않고 단일 문자열 구분자 세트로 간주됩니다.여기서 각 필드는 다음과 같습니다.read입력 라인으로부터의 분할은, 세트내의 임의의 문자(이 예에서는 공백 또는 공백)로 종료할 수 있습니다.

, 실,, of actually of of of의 완전한 $IFS조금 더 관여하고 있습니다.bash 매뉴얼:

셸은 IFS의 각 문자를 딜리미터로 취급하고 다른 확장 결과를 필드 터미네이터로 사용하여 단어로 분할합니다.IFS가 설정되지 않았거나 그 값이 정확히 <space><tab><newline>인 경우 기본적으로는 이전 확장 결과의 시작과 끝에 있는 <space>, <tab> 및 <newline>의 시퀀스는 무시되며 시작 또는 끝에 없는 모든 IFS 문자의 시퀀스는 구분자로 기능합니다.IFS 에 디폴트 이외의 값이 설정되어 있는 경우, 공백 문자가 IFS(IFS 공백 문자)의 값내에 있는 한, 공백 문자<space>,<tab> 및<newline> 의 시퀀스는 워드의 선두와 말미에 무시됩니다.IFS 공백이 아닌 IFS 내의 임의의 문자와 인접한 IFS 공백 문자는 필드를 구분합니다.일련의 IFS 공백 문자는 딜리미터로도 취급됩니다.IFS 값이 늘인 경우 단어 분할은 발생하지 않습니다.

디폴트 인 "null"의 $IFS필드에는, 「IFS 공백 문자」의 세트로부터 모두 1 문자 또는 복수의 문자(,<space>,<>,및<newline>(「newline」) 중 어느 쪽인가에 있는, 행 피드(LF)를 의미하는 「newline」)를 사용할 수 있습니다.$IFS (2IFS 에 존재하는 (2) (2) 「$IFSIFS 공백 문자"가 입력 행에 표시됩니다.

OP의 경우, 이전 단락에서 설명한 두 번째 분리 모드가 입력 문자열에 대해 그가 원하는 것과 같을 수 있지만, 우리가 설명한 첫 번째 분리 모드는 전혀 정확하지 않다고 확신할 수 있습니다.를 들어, 문자열이 「」, 「」, 「」인 경우는 어떻게 됩니까?'Los Angeles, United States, North America'

IFS=', ' read -ra a <<<'Los Angeles, United States, North America'; declare -p a;
## declare -a a=([0]="Los" [1]="Angeles" [2]="United" [3]="States" [4]="North" [5]="America")

2: 이 솔루션을 단일 문자 구분 기호(쉼표 자체, 즉 다음 공간이나 다른 짐 없이)와 함께 사용하는 경우에도$string되어 있습니다. LF가 포함되어 있습니다.readLF를 사용하다readbuiltin은 호출당 한 줄만 처리합니다.이것은 입력의 파이핑 또는 리다이렉트만 하는 경우에도 해당됩니다.read 예에서 하고 있는 것과 같이 스테이트먼트는, 처리되지 않은 입력이 없어지는 것을 보증합니다.전원 코드readbuiltin에는 포함된 명령 구조 내의 데이터 흐름에 대한 지식이 없습니다.

이것이 문제를 일으킬 가능성은 낮다고 주장할 수 있지만, 그래도 가능하면 피해야 할 미묘한 위험입니다.은 이 일이 일어나기 합니다.read빌트인첫 번째는 행으로, 다음은 필드로 분할됩니다.이 OP의 은 1레벨의 분할만을 필요로 .read빌트인은 적절하지 않으므로 피해야 합니다.

3: 이 솔루션의 명백한 잠재적인 문제는 다음과 같습니다.read는 후행 필드가 비어 있으면 항상 드롭합니다.하다, 하다, 하다, 비비비 다다다다 만보다만데모를 소개합니다.

string=', , a, , b, c, , , '; IFS=', ' read -ra a <<<"$string"; declare -p a;
## declare -a a=([0]="" [1]="" [2]="a" [3]="" [4]="b" [5]="c" [6]="" [7]="")

작전본부는 신경 쓰지 않겠지만 그래도 알만한 한계점이야솔루션의 견고성과 범용성을 저하시킵니다.

이 스트링에 입력하기 할 수 .read이치노


오답 #2

string="1:2:3:4:5"
set -f                     # avoid globbing (expansion of *).
array=(${string//:/ })

같은 생각:

t="one,two,three"
a=($(echo $t | tr ',' "\n"))

(주의: 응답자가 생략한 것 같은 명령어 대체 주위에 괄호를 추가했습니다.)

같은 생각:

string="1,2,3,4"
array=(`echo $string | sed 's/,/\n/g'`)

이러한 솔루션은 배열 할당에서 단어 분할을 활용하여 문자열을 여러 필드로 분할합니다.재밌게도, 마치read에서는, 「」도 합니다.$IFS 경우 디폴트값 <space> <tab> <newline>으로 설정되어 있는 것을 암시합니다.따라서 1개 이상의 IFS 문자(모두 공백 문자)의 시퀀스는 필드 딜리미터로 간주됩니다.

두 됩니다.read단어 분할 자체가 하나의 분할 수준에 불과하기 때문입니다. 이전과 각에 이미 '알다'가 포함될 수 것이 입니다.$IFS따라서 단어 분할 작업 중에 부적절하게 분할됩니다.이러한 응답자가 제공하는 샘플 입력 문자열의 경우(얼마나 편리한가...)에는 해당되지 않지만, 물론 이 어구를 사용한 코드 베이스는 나중에 이 가정이 위반될 경우 폭발할 위험이 있다는 사실은 변하지 않습니다.한 번 저의 .'Los Angeles, United States, North America' (오류)'Los Angeles:United States:North America'를 참조해 주세요.

또한 단어 분할에는 보통 파일 이름 확장(경로 이름 확장 또는 globing)이 뒤따릅니다.이 확장으로 인해 문자가 포함된 단어가 손상될 수 있습니다.*,? , 「」[에 어 followed가 붙는다.] ()extglob되면 괄호 에 붙습니다.'프래그먼트'?,*,+,@ , 「」!장갑을 끼다 세 중 첫 는 이 .set -f글로벌화를 무효로 합니다. 말하면,, 「이러다」를 할 만).set +f흐느끼다다만, 로컬 코드의 기본적인 문자열에서 어레이로의 해석 조작을 해킹하기 위해서, 글로벌 셸 설정을 조작하는 것은 바람직하지 않습니다.

이 답변의 또 다른 문제는 빈 필드가 모두 손실된다는 것입니다.이것은 애플리케이션에 따라서는 문제가 될 수도 있고 그렇지 않을 수도 있습니다.

「 」를 하는 것이 .${string//:/ }파라미터 확장의 "pattern substitution" 형식에서는 명령어 대체(셸을 포크)를 호출하거나 파이프라인을 기동하거나 외부 실행 파일을 실행하는 번거로움이 없습니다.tr ★★★★★★★★★★★★★★★★★」sed, (의 경우)tr ★★★★★★★★★★★★★★★★★」sed치환 분할이 「」, 「」로 유효하게 .그렇지 않으면 단어 분할은 명령어 치환에서 유효하게 됩니다.echo명령어를 사용하여 필드 값을 조작할 수 있습니다. ,,$(...)`...`를 사용하면 명령어 치환의 중첩이 심플해지고 텍스트에디터에 의한 구문 하이라이트가 향상되기 때문입니다).


오답 #3

str="a, b, c, d"  # assuming there is a space after ',' as in Q
arr=(${str//,/})  # delete all occurrences of ','

이 답은 2번과 거의 비슷해요.차이점은 응답자가 필드가 2개의 문자로 구분된다고 가정하고, 그 중 하나는 기본값으로 표시됩니다.$IFS 다른 는 아니다그는 패턴 치환 확장을 사용하여 비 IFS 표현 문자를 제거한 후 단어 분할을 사용하여 남아 있는 IFS 표현 구분 문자의 필드를 분할함으로써 이 다소 구체적인 경우를 해결했습니다.

이것은 매우 일반적인 해결책이 아닙니다.게다가 여기서 콤마는 실제로는 「프라이머리」 딜리미터 문자이며, 그것을 제거하고 나서 필드 분할을 위한 스페이스 문자에 의존하는 것은 단순히 잘못된 것이라고 주장할 수 있다.한 번 저의.'Los Angeles, United States, North America'.

워드가되는 일이 만, 「」, 「」, 「」, 「」, 「」, 「」, 「」, 「」와의 에 대해서, 를 무효로 하는 으로, 이것을 수 .set -f 다음에 또 한 번.set +f.

또, 빈 필드는 모두 없어집니다.어플리케이션에 따라서는 문제가 될 수도 있고 문제가 되지 않을 수도 있습니다.


오답 #4

string='first line
second line
third line'

oldIFS="$IFS"
IFS='
'
IFS=${IFS:0:1} # this is useful to format your code with tabs
lines=( $string )
IFS="$oldIFS"

이것은 작업을 완료하기 위해 단어 분할을 사용한다는 에서 #2 및 #3과 유사하지만, 이제 코드가 명시적으로 설정됩니다.$IFS입력 문자열에 있는 단일 문자 필드 딜리미터만 포함합니다.OP의 쉼표 구분 기호와 같은 다중 문자 필드 구분 기호에서는 이 기능이 작동하지 않음을 반복해야 합니다.그러나 이 예에서 사용되는 LF와 같은 단일 문자 딜리미터의 경우 실제로는 완벽에 가깝습니다.이전 오답에서 보듯이 필드를 실수로 중간에 분할할 수 없으며 필요에 따라 분할할 수 있는 레벨은 1개뿐입니다.

가지 바와 같이 을 받는 단, 이 한 번 ""를 "critical 스테이트먼트"로 할 수 .다만, 이 문제는, critical 스테이트먼트를 로 둘러싸는 것으로 해결할 수 있습니다.set -f ★★★★★★★★★★★★★★★★★」set +f.

또 다른 잠재적인 문제는 LF가 앞서 정의한 'IFS 공백 문자'로 인정되기 때문에 #2 및 #3과 마찬가지로 모든 빈 필드가 손실된다는 것입니다.이 딜리미터가 실제로는 문제가 되지 않습니다.IFS 공백 문자"와 어플리케이션에 따라서는 문제가 되지 않을 수 있지만 솔루션의 범용성은 무효화됩니다.

한 의 딜리미터가 , 그가 '자, '비'자, '비'자, '비'자, '비'자, '비'자, '비'자, '비'자, '비'자, '비'자, '비'자, '비'자, '비'자, '비'자'자, '비'자, '비'자, '비'자, '비'자, '비'자, '비'자'자, 즉 '비'자, '비자, '비자, '비자, '비' critical critical critical critical critical critical critical critical critical critical critical critical critude로 랩합니다.set -f ★★★★★★★★★★★★★★★★★」set +f이 솔루션은 동작하지만, 그 이외에는 동작하지 않습니다.

참고로 를 할당하는 은 (「」의 「LF」의 「LF」의 「LF」의 「LF」의 「LF」의 「LF」의 「LF」의 「LF」의 「LF」의 「LF」의 「LF」의 사용으로 간단하게 실시할 수 $'...' " 、 " ) 。IFS=$'\n';


오답 #5

countries='Paris, France, Europe'
OIFS="$IFS"
IFS=', ' array=($countries)
IFS="$OIFS"

같은 생각:

IFS=', ' eval 'array=($string)'

이 솔루션은 사실상 #1과 #1의 혼재입니다.$IFS(단어 분할을 사용하여 문자열을 필드로 분할함) 및 #2-4를 지정합니다.이것 때문에, 그것은 모든 것 중에서 가장 나쁜 것과 같은, 위의 오답들을 괴롭히는 대부분의 문제들로 고통받고 있다.

두 에서는 '아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아,eval콜 인수는 단일 문자열 리터럴이므로 콜은 완전히 불필요합니다. '아까부터'를 쓰면 에 띄지 않는 eval이런 식으로.일반적으로 변수 할당으로만 구성된 단순 명령어를 실행하면 실제 명령어 뒤에 따라오지 않고 할당이 셸 환경에서 활성화됩니다.

IFS=', '; ## changes $IFS in the shell environment

이는 단순 명령에 여러 변수 할당이 포함된 경우에도 마찬가지입니다. 다시 말하지만 명령어가 없는 한 모든 변수 할당은 셸 환경에 영향을 미칩니다.

IFS=', ' array=($countries); ## changes both $IFS and $array in the shell environment

단, 변수 할당이 명령어 이름에 부가되어 있는 경우(이것은 「프리픽스 할당」이라고 부릅니다), 셸 환경에는 영향을 주지 않고, 빌트인인지 외부인지에 관계없이 실행되는 명령어 환경에만 영향을 줍니다.

IFS=', ' :; ## : is a builtin command, the $IFS assignment does not outlive it
IFS=', ' env; ## env is an external command, the $IFS assignment does not outlive it

bash 매뉴얼의 관련 인용:

명령어 이름이 표시되지 않으면 변수 할당이 현재 셸 환경에 영향을 줍니다.그렇지 않으면 변수가 실행된 명령 환경에 추가되어 현재 셸 환경에는 영향을 주지 않습니다.

이 할당을 변경할 수 .$IFS할 수 있기 앤 리스토어의 할 수 .$OIFS은 첫 변종입니다.그러나 서 우리가 는 우리가 하지 않고 할 수 입니다.$IFS임시 할당입니다.이렇게 생각할 수 있습니다.이렇게 no-op 명령어를 스테이트먼트에 추가하여$IFS임시 할당?이것은 동작하지 않습니다.★★★★★★★★★★★★★★★★,$array임시 할당도 가능합니다.

IFS=', ' array=($countries) :; ## fails; new $array value never escapes the : command

그래서 우리는 사실상 교착상태에 빠졌고, 약간 난관에 봉착했다., ★★★★★★★★★★★★★★★★★★★★★.eval그셸 , 「 코드」를 할 수 .을 사용하다$array「 」의 eval.$IFS .eval 않습니다.eval명령어를 입력합니다.의 두 입니다.

IFS=', ' eval 'array=($string)'; ## $IFS does not outlive the eval command, but $array does

보다시피, 이것은 꽤 교묘한 속임수이며, (적어도 할당 효과와 관련하여) 필요한 것을 확실히 달성합니다. 하지 않는다eval; 보안 위협으로부터 보호하기 위해 인수 문자열을 한 줄로 구분하는 데 주의합니다.

그러나 다시 한 번 말하지만, "세계 최악의" 문제가 집중되어 있기 때문에, 이것은 여전히 OP의 요구 사항에 대한 잘못된 대답입니다.


오답 #6

IFS=', '; array=(Paris, France, Europe)

IFS=' ';declare -a array=(Paris France Europe)

음... 뭐라고요?OP에는 배열로 구문 분석해야 하는 문자열 변수가 있습니다.이 "answer"는 입력 문자열의 문자 그대로를 배열 리터럴에 붙여넣는 것으로 시작합니다.그렇게 하는 것도 한 방법인 것 같아요.

.$IFS가 아닙니다.bash 파싱은 bash 파싱에 영향을 줍니다.이것은 사실이 아닙니다.에서 : bash " " :

IFS 확장 후 단어 분할 및 read builtin 명령어로 행 분할에 사용되는 내부 필드 구분 기호.기본값은 <space> <tab> <newline> 입니다.

그...$IFSspecial variable은 실제로 두 가지 컨텍스트에서만 사용됩니다. (1) 확장 후에 실행되는 단어 분할(bash 소스 코드를 해석할 때 아님) 및 (2) 입력 행을 에 의해 단어로 분할하기 위한 경우read빌트인

이걸 좀 더 명확히 해 볼게요.파싱과 실행을 구분하는 것이 좋을 것 같습니다.Bash는 먼저 소스 코드를 구문 분석해야 합니다.이것은 명백히 구문 분석 이벤트입니다.그리고 나중에 코드를 실행합니다.이것은 확장이 그림으로 들어오는 경우입니다.확장은 실제로 실행 이벤트입니다.게다가, 나는 그 설명에 대해 이의를 제기한다.$IFS위에서 인용한 변수입니다.단어 분할은 확장 후에 실행된다고 하기보다는 확장 중에 실행된다고 할 수 있습니다.또는 보다 정확하게는 단어 분할이 확장 프로세스의 일부라고 할 수 있습니다."word split"이라는 문구는 이 확장 단계만을 가리킵니다.bash 소스 코드의 해석을 가리킬 때는 사용하지 마십시오.다만, 유감스럽게도 문서는 "split"과 "words"라는 단어를 많이 사용하는 것 같습니다.다음은 linux.die에서 발췌한 내용입니다.bash 매뉴얼의 net 버전:

확장은 명령줄을 단어로 분할한 후 수행됩니다.수행되는 확장에는 브레이스 확장, 칠드 확장, 파라미터변수 확장, 명령어 대체, 산술 확장, 단어 분할 및 경로 이름 확장의 7가지 종류가 있습니다.

확장 순서는 가새 확장, 칠드 확장, 파라미터 및 변수 확장, 산술 확장 및 명령어 대체(왼쪽에서 오른쪽으로 실행), 단어 분할 및 경로 이름 확장입니다.

설명서의 GNU 버전은 확장 섹션의 첫 번째 문장에서 "words" 대신 "tokens"라는 단어를 선택하기 때문에 약간 더 낫다고 주장할 수 있습니다.

토큰으로 분할된 후 명령줄에서 확장이 수행됩니다.

은 '아까운 것'입니다$IFSbash가 소스 코드를 해석하는 방법은 변경되지 않습니다.bash 소스 코드의 해석은 명령어시퀀스, 명령어리스트, 파이프라인, 파라미터 확장, 산술 치환, 명령어 치환 등 셸 문법의 다양한 요소를 인식하는 매우 복잡한 프로세스입니다.대부분의 경우 변수 할당과 같은 사용자 수준의 액션으로는 bash 구문 분석 프로세스를 변경할 수 없습니다(실제로 이 규칙에는 몇 가지 사소한 예외가 있습니다.예를 들어 다양한 셸 설정을 참조해 주세요.이는 해석 동작의 특정 측면을 즉시 변경할 수 있습니다).이 복잡한 해석 프로세스에서 발생하는 업스트림 "words"/"token"은 위의 문서 발췌에서 설명한 바와 같이 일반적인 "expansion" 프로세스에 따라 전개됩니다.여기서 확장(확장?) 텍스트를 다운스트림 단어로 분할하는 것은 그 과정의 한 단계일 뿐입니다.단어 분할은 이전 확장 단계에서 뱉은 텍스트에만 영향을 줍니다. 원본 bytestream에서 바로 구문 분석된 리터럴 텍스트에는 영향을 주지 않습니다.


오답 #7

string='first line
        second line
        third line'

while read -r line; do lines+=("$line"); done <<<"$string"

이치 이제 을 살펴보도록 하겠습니다.read '아까'라고 않았나요read한 단계만 필요한 경우 두 가지 수준의 분할을 수행하기 때문에 적합하지 않은가?이란, 불러도 거죠.read이러한 방법으로 효율적으로 한 가지 수준의 분할만 수행할 수 있으며, 특히 호출당 하나의 필드만 분할함으로써 루프에서 반복 호출해야 하는 비용이 발생합니다.★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

하지만 문제가 있다.첫 번째: 적어도1개의 NAME 인수를 지정한 경우read입력 문자열에서 분리된 각 필드의 선행 및 후행 공백을 자동으로 무시합니다.이것은, 다음의 경우에 발생합니다.$IFS는 이 투고에서 설명한 바와 같이 디폴트값으로 설정되어 있는지 여부를 나타냅니다.OP는 자신의 특정 사용 예에 대해 이 문제에 대해 신경 쓰지 않을 수 있으며, 실제로 이 기능은 구문 분석 동작의 바람직한 기능일 수 있습니다.그러나 문자열을 필드에 구문 분석하려는 모든 사용자가 원하는 것은 아닙니다.: 입니다. 하다readname 인수를 0으로 전달합니다.이 경우,read 행 "이러한 행은(는) 입력 스트림에서 얻은 입력 행 전체를 합니다.$REPLY또한 값에서 선행 및 후행 공백을 제거하지 않습니다.이것은 매우 견고한 사용법입니다.read 줍니다.을 사용하다

string=$'  a  b  \n  c  d  \n  e  f  '; ## input string

a=(); while read -r line; do a+=("$line"); done <<<"$string"; declare -p a;
## declare -a a=([0]="a  b" [1]="c  d" [2]="e  f") ## read trimmed surrounding whitespace

a=(); while read -r; do a+=("$REPLY"); done <<<"$string"; declare -p a;
## declare -a a=([0]="  a  b  " [1]="  c  d  " [2]="  e  f  ") ## no trimming

이 솔루션의 두 번째 문제는 OP의 쉼표 공간과 같은 사용자 지정 필드 구분자를 실제로 다루지 않는다는 것입니다.이전과 같이 다중 문자 구분 기호는 지원되지 않습니다.이것은 이 솔루션에서는 유감스러운 제한입니다..-d어떤 일이 일어나는지 보세요.

string='Paris, France, Europe';
a=(); while read -rd,; do a+=("$REPLY"); done <<<"$string"; declare -p a;
## declare -a a=([0]="Paris" [1]=" France")

예상대로, 어카운팅되지 않은 주변 공백이 필드 값에 삽입되고, 따라서 이 값은 트리밍 작업을 통해 나중에 수정되어야 합니다(이것은 while-loop에서도 직접 수행될 수 있습니다).하지만 또 다른 명백한 오류가 있습니다.★★★★★★★★★★★★★★★★★★★!떻게 ???은 '먹다'입니다.read는, 최종 하지 않고, 파일 종료에 이 경우는 오브 스트링이라고 수 ), 합니다.이--- while-loop이--이이이이이이이이이이이이이이이이이이이.

구분자는 LF를 이 됩니다.그 차이는 필드 구분자가 LF로 간주되었다는 것입니다.LF lf lf lf lf lf lf lf lf lf lf lf 。-d 및 "" " " " " "<<<("여기서 문자열") 메커니즘은 LF를 명령어에 입력으로 공급하기 직전에 자동으로 문자열에 추가합니다.따라서 이 경우 실수로 입력에 더미 터미네이터를 추가하여 최종 필드의 드롭 문제를 해결했습니다.이 솔루션을 '더미 터미네이터' 솔루션이라고 부릅니다.더미 터미네이터 솔루션을 커스텀 딜리미터에 수동으로 적용할 수 있습니다.입력 스트링에 더미 터미네이터 솔루션을 인스턴스화할 때 직접 연결합니다.

a=(); while read -rd,; do a+=("$REPLY"); done <<<"$string,"; declare -p a;
declare -a a=([0]="Paris" [1]=" France" [2]=" Europe")

아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아. 다른 방법은 다 (1)이 되다, 둘 다 while-loop일 입니다.read와 (반환된 장애 (2)$REPLY즉 empty입니다.의미read파일 종료를 누르기 전에 문자를 읽을 수 없었습니다. 모::

a=(); while read -rd,|| [[ -n "$REPLY" ]]; do a+=("$REPLY"); done <<<"$string"; declare -p a;
## declare -a a=([0]="Paris" [1]=" France" [2]=$' Europe\n')

이 에서는 비밀 LF가 이 에 부가됩니다.<<< 전에 한 바와 으로 떼어낼 수 할 수 있기 .물론 조금 전에 설명한 바와 같이 명시적인 트리밍 작업을 통해 개별적으로 제거할 수 있지만, 수동 더미 터미네이터 접근법으로 직접 해결할 수 있으므로 그대로 사용할 수 있습니다.수동 더미 터미네이터 솔루션은 이 두 가지 문제(드롭 최종 필드 문제와 부가 LF 문제)를 한 번에 해결한다는 점에서 매우 편리합니다.

따라서 전반적으로 이 솔루션은 매우 강력한 솔루션입니다.유일한 약점은 다중 문자의 구분자를 지원하지 않는다는 것입니다. 나중에 설명하겠습니다.


오답 #8

string='first line
        second line
        third line'

readarray -t lines <<<"$string"

(이것은, 실제로는 #7과 같은 투고입니다.답변자는 같은 투고에 2개의 솔루션을 제공했습니다.)

readarray "의 입니다.mapfile 하여 루프,하지 않는 bytestream으로 설정합니다.프,, ,,, ,환등 환등등 다없다다없입력 문자열에서 몰래 여백을 제거하지 않습니다. (만약에)-O지정되지 않음)를 사용하면 타깃 배열을 할당하기 전에 손쉽게 클리어할 수 있습니다.하지만 아직 완벽하지 않기 때문에 오답이라고 비판하고 있습니다.

먼저 이 '아까', '', '아까', '아까', '아까', '아까', '아까', '의 동작에 주의해 read할 때 " " "readarray후행 필드가 비어 있는 경우 해당 필드가 폐기됩니다.다시 말씀드리지만, 이는 OP에 대한 우려는 아닐 수 있지만 일부 사용 사례에 대한 우려일 수 있습니다.잠시 후에 이것에 대해 다시 이야기하겠습니다.

둘째, 이전과 같이 다중 문자 구분 기호를 지원하지 않습니다.이것도 잠시 후에 수정하겠습니다.

셋째, 기술된 솔루션은 OP의 입력 문자열을 구문 분석하지 않으며, 실제로는 그대로 사용하여 구문 분석할 수 없습니다.이것도 잠시 자세히 설명하겠습니다.

위와 같은 이유로 OP의 질문에 대한 "오답"이라고 생각한다.아래에 제가 옳다고 생각하는 것을 말씀드리겠습니다.


정답

여기 8번째를 기능시키기 위한 순진한 시도가 있습니다.-d★★★★

string='Paris, France, Europe';
readarray -td, a <<<"$string"; declare -p a;
## declare -a a=([0]="Paris" [1]=" France" [2]=$' Europe\n')

에서 얻은 하다는 것을 알 수 .read7번에서 설명한 솔루션.수동 더미 터미네이터 트릭으로 이 문제를 해결할 수 있습니다.

readarray -td, a <<<"$string,"; declare -p a;
## declare -a a=([0]="Paris" [1]=" France" [2]=" Europe" [3]=$'\n')

것은 '이렇게 하다'는 것입니다.readarray<<<리다이렉션 연산자가 입력 문자열에 LF를 추가했기 때문에 후행 필드가 비어 있지 않았습니다(그렇지 않으면 LF가 삭제되었을 수 있습니다).최종 어레이 요소를 명시적으로 설정 해제함으로써 이 문제를 해결할 수 있습니다.

readarray -td, a <<<"$string,"; unset 'a[-1]'; declare -p a;
## declare -a a=([0]="Paris" [1]=" France" [2]=" Europe")

남은 문제는 (1) 삭제해야 하는 외부 공백과 (2) 다중 문자 구분 기호가 지원되지 않는 문제뿐입니다.

물론 공백은 나중에 잘라낼 수 있습니다(예: Bash 변수에서 공백을 잘라내는 방법? 참조).하지만 다중 문자 구분 기호를 해킹할 수 있다면 두 가지 문제를 한 번에 해결할 수 있습니다.

유감스럽게도 다중 문자 구분 기호를 사용할 수 있는 직접적인 방법은 없습니다.제가 생각한 최선의 해결책은 입력 문자열의 내용과 충돌하지 않도록 다중 문자 딜리미터를 단일 문자 딜리미터로 대체하는 것입니다.이 보증이 있는 문자는 NUL 바이트뿐입니다.그 이유는 bash에서는 변수에 NUL 바이트를 포함할 수 없기 때문입니다(우연히 zsh는 아닙니다.이 전처리 순서는 프로세스 치환으로 인라인으로 실행할 수 있습니다.awk를 사용하는 방법은 다음과 같습니다.

readarray -td '' a < <(awk '{ gsub(/, /,"\0"); print; }' <<<"$string, "); unset 'a[-1]';
declare -p a;
## declare -a a=([0]="Paris" [1]="France" [2]="Europe")

드디어 왔군!이 솔루션은 필드를 중간에 잘못 분할하지 않으며, 너무 빨리 잘라내지 않으며, 빈 필드를 삭제하지 않으며, 파일 이름 확장 시 자동으로 손상되지 않으며, 선두 및 후행 공백이 자동으로 제거되지 않으며, 끝에 보관 LF를 남기지 않으며, 루프가 필요하지 않으며, 단일 문자 구분 기호로 만족하지 않습니다.


트리밍 솔루션

는 잘 알려지지 않은 .-C callback " " " "readarray아쉽게도 Stack Overflow의 엄격한 3만 글자 게시 제한에 대한 공간이 부족하기 때문에 설명할 수 없습니다.그건 독자를 위한 연습으로 남겨두겠습니다.

function mfcb { local val="$4"; "$1"; eval "$2[$3]=\$val;"; };
function val_ltrim { if [[ "$val" =~ ^[[:space:]]+ ]]; then val="${val:${#BASH_REMATCH[0]}}"; fi; };
function val_rtrim { if [[ "$val" =~ [[:space:]]+$ ]]; then val="${val:0:${#val}-${#BASH_REMATCH[0]}}"; fi; };
function val_trim { val_ltrim; val_rtrim; };
readarray -c1 -C 'mfcb val_trim a' -td, <<<"$string,"; unset 'a[-1]'; declare -p a;
## declare -a a=([0]="Paris" [1]="France" [2]="Europe")

IFS 를 설정하지 않는 방법을 다음에 나타냅니다.

string="1:2:3:4:5"
set -f                      # avoid globbing (expansion of *).
array=(${string//:/ })
for i in "${!array[@]}"
do
    echo "$i=>${array[i]}"
done

이 아이디어는 문자열 치환을 사용하는 것입니다.

${string//substring/replacement}

$sqring의 모든 일치를 공백으로 대체한 후 대체 문자열을 사용하여 배열을 초기화하려면 다음과 같이 하십시오.

(element1 element2 ... elementN)

주의: 이 답변에서는 split+glob 연산자를 사용합니다.따라서 일부 문자(예:* 스크립트에서는하는 것이

t="one,two,three"
a=($(echo "$t" | tr ',' '\n'))
echo "${a[2]}"

3장 인쇄

특히 세퍼레이터가 캐리지 리턴인 경우, 수용된 답변에 기재된 방법이 작동하지 않는 경우가 종종 있었습니다.
요.

string='first line
second line
third line'

oldIFS="$IFS"
IFS='
'
IFS=${IFS:0:1} # this is useful to format your code with tabs
lines=( $string )
IFS="$oldIFS"

for line in "${lines[@]}"
    do
        echo "--> $line"
done

허용되는 답변은 한 줄의 값에 대해 작동합니다.
" " " " " " : "

string='first line
        second line
        third line'

모든 회선을 가져오려면 매우 다른 명령어가 필요합니다.

while read -r line; do lines+=("$line"); done <<<"$string"

또는 훨씬 단순한 bash readarray:

readarray -t lines <<<"$string"

인쇄 기능을 이용하면, 모든 행의 인쇄가 매우 간단합니다.

printf ">[%s]\n" "${lines[@]}"

>[first line]
>[        second line]
>[        third line]

macOS를 사용하고 있고 readarray를 사용할 수 없다면, 간단히 이것을 할 수 있습니다.

MY_STRING="string1 string2 string3"
array=($MY_STRING)

요소를 반복하려면:

for element in "${array[@]}"
do
    echo $element
done

OSX에서는 다음과 같이 동작합니다.

string="1 2 3 4 5"
declare -a array=($string)

문자열의 딜리미터가 다를 경우 첫 번째 딜리미터를 공백으로 바꿉니다.

string="1,2,3,4,5"
delimiter=","
declare -a array=($(echo $string | tr "$delimiter" " "))

심플 :-)

이는 Jmoney38의 접근법과 유사하지만 sed를 사용한다.

string="1,2,3,4"
array=(`echo $string | sed 's/,/\n/g'`)
echo ${array[0]}

인쇄물 1

입니다.", "를 사용한 .IFSIFS는 문자열이 아니라 여러 문자의 집합이기 때문에 여러 문자의 딜리미터는 본질적으로 잘못되어 있습니다.

「 」를 할당했을 IFS=", "."," ★★★" " .", "

하시면 됩니다.awk ★★★★★★★★★★★★★★★★★」sed프로세스 치환을 사용하여 문자열을 분할합니다.

#!/bin/bash

str="Paris, France, Europe"
array=()
while read -r -d $'\0' each; do   # use a NUL terminated field separator 
    array+=("$each")
done < <(printf "%s" "$str" | awk '{ gsub(/,[ ]+|$/,"\0"); print }')
declare -p array
# declare -a array=([0]="Paris" [1]="France" [2]="Europe") output

Bash에서 직접 regex를 사용하는 것이 더 효율적입니다.

#!/bin/bash

str="Paris, France, Europe"

array=()
while [[ $str =~ ([^,]+)(,[ ]+|$) ]]; do
    array+=("${BASH_REMATCH[1]}")   # capture the field
    i=${#BASH_REMATCH}              # length of field + delimiter
    str=${str:i}                    # advance the string by that length
done                                # the loop deletes $str, so make a copy if needed

declare -p array
# declare -a array=([0]="Paris" [1]="France" [2]="Europe") output...

두 번째 폼에서는 서브셸이 없고 본질적으로 더 빠릅니다.


bgoldst에 의한 편집:다음 벤치마크에 대해 설명하겠습니다.readarray에 대한 과 dawg의 regex도 시켰습니다.read솔루션(주의: 솔루션과의 조화를 높이기 위해 regex 솔루션을 약간 수정했습니다) (게시물 아래 코멘트 참조):

## competitors
function c_readarray { readarray -td '' a < <(awk '{ gsub(/, /,"\0"); print; };' <<<"$1, "); unset 'a[-1]'; };
function c_read { a=(); local REPLY=''; while read -r -d ''; do a+=("$REPLY"); done < <(awk '{ gsub(/, /,"\0"); print; };' <<<"$1, "); };
function c_regex { a=(); local s="$1, "; while [[ $s =~ ([^,]+),\  ]]; do a+=("${BASH_REMATCH[1]}"); s=${s:${#BASH_REMATCH}}; done; };

## helper functions
function rep {
    local -i i=-1;
    for ((i = 0; i<$1; ++i)); do
        printf %s "$2";
    done;
}; ## end rep()

function testAll {
    local funcs=();
    local args=();
    local func='';
    local -i rc=-1;
    while [[ "$1" != ':' ]]; do
        func="$1";
        if [[ ! "$func" =~ ^[_a-zA-Z][_a-zA-Z0-9]*$ ]]; then
            echo "bad function name: $func" >&2;
            return 2;
        fi;
        funcs+=("$func");
        shift;
    done;
    shift;
    args=("$@");
    for func in "${funcs[@]}"; do
        echo -n "$func ";
        { time $func "${args[@]}" >/dev/null 2>&1; } 2>&1| tr '\n' '/';
        rc=${PIPESTATUS[0]}; if [[ $rc -ne 0 ]]; then echo "[$rc]"; else echo; fi;
    done| column -ts/;
}; ## end testAll()

function makeStringToSplit {
    local -i n=$1; ## number of fields
    if [[ $n -lt 0 ]]; then echo "bad field count: $n" >&2; return 2; fi;
    if [[ $n -eq 0 ]]; then
        echo;
    elif [[ $n -eq 1 ]]; then
        echo 'first field';
    elif [[ "$n" -eq 2 ]]; then
        echo 'first field, last field';
    else
        echo "first field, $(rep $[$1-2] 'mid field, ')last field";
    fi;
}; ## end makeStringToSplit()

function testAll_splitIntoArray {
    local -i n=$1; ## number of fields in input string
    local s='';
    echo "===== $n field$(if [[ $n -ne 1 ]]; then echo 's'; fi;) =====";
    s="$(makeStringToSplit "$n")";
    testAll c_readarray c_read c_regex : "$s";
}; ## end testAll_splitIntoArray()

## results
testAll_splitIntoArray 1;
## ===== 1 field =====
## c_readarray   real  0m0.067s   user 0m0.000s   sys  0m0.000s
## c_read        real  0m0.064s   user 0m0.000s   sys  0m0.000s
## c_regex       real  0m0.000s   user 0m0.000s   sys  0m0.000s
##
testAll_splitIntoArray 10;
## ===== 10 fields =====
## c_readarray   real  0m0.067s   user 0m0.000s   sys  0m0.000s
## c_read        real  0m0.064s   user 0m0.000s   sys  0m0.000s
## c_regex       real  0m0.001s   user 0m0.000s   sys  0m0.000s
##
testAll_splitIntoArray 100;
## ===== 100 fields =====
## c_readarray   real  0m0.069s   user 0m0.000s   sys  0m0.062s
## c_read        real  0m0.065s   user 0m0.000s   sys  0m0.046s
## c_regex       real  0m0.005s   user 0m0.000s   sys  0m0.000s
##
testAll_splitIntoArray 1000;
## ===== 1000 fields =====
## c_readarray   real  0m0.084s   user 0m0.031s   sys  0m0.077s
## c_read        real  0m0.092s   user 0m0.031s   sys  0m0.046s
## c_regex       real  0m0.125s   user 0m0.125s   sys  0m0.000s
##
testAll_splitIntoArray 10000;
## ===== 10000 fields =====
## c_readarray   real  0m0.209s   user 0m0.093s   sys  0m0.108s
## c_read        real  0m0.333s   user 0m0.234s   sys  0m0.109s
## c_regex       real  0m9.095s   user 0m9.078s   sys  0m0.000s
##
testAll_splitIntoArray 100000;
## ===== 100000 fields =====
## c_readarray   real  0m1.460s   user 0m0.326s   sys  0m1.124s
## c_read        real  0m2.780s   user 0m1.686s   sys  0m1.092s
## c_regex       real  17m38.208s   user 15m16.359s   sys  2m19.375s
##

@bgoldst의 인기 있는 답변에서 "정답"의 상대적인 퍼포먼스가 궁금해서 루프를 명확하게 해독하고 있기 때문에 3개의 순수한 bash 구현에 대해 간단한 벤치마크를 실시했습니다.

요약하면 다음과 같습니다.

  1. 문자열 길이가 4k 미만일 경우 순수 배시가 gawk보다 빠릅니다.
  2. 딜리미터 길이 < 10 및 문자열 길이 < 256k의 경우 순수 bash는 gawk와 동일합니다.
  3. 딜리미터 길이>> 10 및 스트링 길이< 64k의 경우 순수 bash는 "허용"이며 gawk는 5배 미만입니다.
  4. string length가 512k 이하일 경우 gawk는 "적용"입니다.

임의로 "acceptable"을 "takes < 0.5s to split the string"으로 정의합니다.


이 문제는 bash 문자열을 사용하여 임의의 길이의 딜리미터 문자열(regex가 아님)을 사용하여 bash 배열로 분할하는 것입니다.

# in: $1=delim, $2=string
# out: sets array a

순수한 bash 구현은 다음과 같습니다.

# naive approach - slow
split_byStr_bash_naive(){
    a=()
    local prev=""
    local cdr="$2"
    [[ -z "${cdr}" ]] && a+=("")
    while [[ "$cdr" != "$prev" ]]; do
        prev="$cdr"
        a+=( "${cdr%%"$1"*}" )
        cdr="${cdr#*"$1"}"
    done
    # echo $( declare -p a | md5sum; declare -p a )
}
# use lengths wherever possible - faster
split_byStr_bash_faster(){
    a=()
    local car=""
    local cdr="$2"
    while
        car="${cdr%%"$1"*}"
        a+=("$car")
        cdr="${cdr:${#car}}"
        (( ${#cdr} ))
    do
        cdr="${cdr:${#1}}"
    done
    # echo $( declare -p a | md5sum; declare -p a )
}
# use pattern substitution and readarray - fastest
split_byStr_bash_sub(){
        a=()
        local delim="$1" string="$2"

        delim="${delim//=/=-}"
        delim="${delim//$'\n'/=n}"

        string="${string//=/=-}"
        string="${string//$'\n'/=n}"

        readarray -td $'\n' a <<<"${string//"$delim"/$'\n'}"

        local len=${#a[@]} i s
        for (( i=0; i<len; i++ )); do
                s="${a[$i]//=n/$'\n'}"
                a[$i]="${s//=-/=}"
        done
        # echo $( declare -p a | md5sum; declare -p a )
}

번째 " " "-zNaigive 버전의 test에서는 제로 길이의 문자열이 전달되는 경우를 처리합니다.테스트하지 않으면 출력 배열은 비어 있습니다.테스트를 사용하면 배열에는 단일 제로 길이 요소가 포함됩니다.

<고객명>의 치환readarraywhile read10%


사용한 gawk 실장은 다음과 같습니다.

split_byRE_gawk(){
    readarray -td '' a < <(awk '{gsub(/'"$1"'/,"\0")}1' <<<"$2$1")
    unset 'a[-1]'
    # echo $( declare -p a | md5sum; declare -p a )
}

gawk는 regex를 예상하고 gawk 특수문자는 문제를 일으킬 수 있으므로 일반적인 경우 delim 인수를 삭제해야 합니다.또, 현재 실장에서는 딜리미터의 새로운 행이 올바르게 처리되지 않습니다.

gawk가 사용되고 있기 때문에 더 많은 임의 딜리미터를 처리하는 일반 버전은 다음과 같습니다.

split_byREorStr_gawk(){
    local delim=$1
    local string=$2
    local useRegex=${3:+1}  # if set, delimiter is regex

    readarray -td '' a < <(
        export delim
        gawk -v re="$useRegex" '
            BEGIN {
                RS = FS = "\0"
                ORS = ""
                d = ENVIRON["delim"]

                # cf. https://stackoverflow.com/a/37039138
                if (!re) gsub(/[\\.^$(){}\[\]|*+?]/,"\\\\&",d)
            }
            gsub(d"|\n$","\0")
        ' <<<"$string"
    )
    # echo $( declare -p a | md5sum; declare -p a )
}

또는 Perl에서도 같은 아이디어:

split_byREorStr_perl(){
    local delim=$1
    local string=$2
    local regex=$3  # if set, delimiter is regex

    readarray -td '' a < <(
        export delim regex
        perl -0777pe '
            $d = $ENV{delim};
            $d = "\Q$d\E" if ! $ENV{regex};
            s/$d|\n$/\0/g;
        ' <<<"$string"
    )
    # echo $( declare -p a | md5sum; declare -p a )
}

실장에서는 md5sum을 개별적으로 비교하여 테스트한 결과 동일한 출력이 생성됩니다.

입력이 애매한 경우(@bgoldst의 표현대로 논리적으로 올바르지 않은 경우) 동작은 약간 분산됩니다.를 들어 딜리미터가 있는 -- 문자열 " " " "a- ★★★★★★★★★★★★★★★★★」a---:

  • @ 반환: @goldst 코드 @ @ :declare -a a=([0]="a") ★★★★★★★★★★★★★★★★★」declare -a a=([0]="a" [1]="")
  • return : 산산반 mine mine mine :declare -a a=([0]="a-") ★★★★★★★★★★★★★★★★★」declare -a a=([0]="a" [1]="-")

인수는 다음에서 단순한 Perl 스크립트를 사용하여 파생되었습니다.

delim="-=-="
base="ABCDEFGHIJKLMNOPQRSTUVWXYZ012345"

다음은 3가지 다른 유형의 문자열 및 딜리미터 인수에 대한 타이밍 결과(초단위) 표입니다.

  • #s - string " " " "
  • #d 인수 - " " " " "
  • = - 퍼포먼스 손익분기점
  • ! ' 가능한'이 어딘가에 - 이 근처에요.
  • !! ' 가능한' 제한(가우크은 이 입니다. - 가우크(가우크)는요.
  • -이 너무 . - 기능이 너무 오래 걸렸습니다.
  • <!>를 실행하지 . - gawk 명령어를 실행하지 못했습니다.

유형 1

d=$(perl -e "print( '$delim' x (7*2**$n) )")
s=$(perl -e "print( '$delim' x (7*2**$n) . '$base' x (7*2**$n) )")
n #s #d 째려보다 b_sub b_internals. b_internals.
0 252 28 0.002 0.000 0.000 0.000
1 504 56 0.005 0.000 0.000 0.001
2 1008 112 0.005 0.001 0.000 0.003
3 2016 224 0.006 0.001 0.000 0.009
4 4032 448 0.007 0.002 0.001 0.048
= 5 8064 896 0.014 0.008 0.005 0.377
6 16128 1792 0.018 0.029 0.017 (2.214)
7 32256 3584 0.033 0.057 0.039 (15.16)
! 8 64512 7168 0.063 0.214 0.128 -
9 129024 14336 0.111 (0.826) (0.602) -
10 258048 28672 0.214 (3.383) (2.652) -
!! 11 516096 57344 0.430 (13.46) (11.00) -
12 1032192 114688 (0.834) (58.38) - -
13 2064384 229376 <!> (228.9) - -

유형 2

d=$(perl -e "print( '$delim' x ($n) )")
s=$(perl -e "print( ('$delim' x ($n) . '$base' x $n ) x (2**($n-1)) )")
n #s #d 째려보다 b_sub b_internals. b_internals.
0 0 0 0.003 0.000 0.000 0.000
1 36 4 0.003 0.000 0.000 0.000
2 144 8 0.005 0.000 0.000 0.000
3 432 12 0.005 0.000 0.000 0.000
4 1152 16 0.005 0.001 0.001 0.002
5 2880 20 0.005 0.001 0.002 0.003
6 6912 24 0.006 0.003 0.009 0.014
= 7 16128 28 0.012 0.012 0.037 0.044
8 36864 32 0.023 0.044 0.167 0.187
! 9 82944 36 0.049 0.192 (0.753) (0.840)
10 184320 40 0.097 (0.925) (3.682) (4.016)
11 405504 44 0.204 (4.709) (18.00) (19.58)
!! 12 884736 48 0.444 (22.17) - -
13 1916928 52 (1.019) (102.4) - -

형식 3.

d=$(perl -e "print( '$delim' x (2**($n-1)) )")
s=$(perl -e "print( ('$delim' x (2**($n-1)) . '$base' x (2**($n-1)) ) x ($n) )")
n #s #d 째려보다 b_sub b_internals. b_internals.
0 0 0 0.000 0.000 0.000 0.000
1 36 4 0.004 0.000 0.000 0.000
2 144 8 0.003 0.000 0.000 0.000
3 432 16 0.003 0.000 0.000 0.000
4 1152 32 0.005 0.001 0.001 0.002
5 2880 64 0.005 0.002 0.001 0.003
6 6912 128 0.006 0.003 0.003 0.014
= 7 16128 256 0.012 0.011 0.010 0.077
8 36864 512 0.023 0.046 0.046 (0.513)
! 9 82944 1024 0.049 0.195 0.197 (3.850)
10 184320 2048 0.103 (0.951) (1.061) (31.84)
11 405504 4096 0.222 (4.796) - -
!! 12 884736 8192 0.473 (22.88) - -
13 1916928 16384 (1.126) (105.4) - -

구분자 길이 1의 요약입니다.10

짧은 딜리미터는 긴 딜리미터보다 많은 경우가 있으므로 1에서 10 사이의 딜리미터 길이(2에 대한 결과)를 다음에 요약합니다.9는 대부분 매우 유사한 것으로 생략되었다).

s1=$(perl -e "print( '$d' . '$base' x (7*2**$n) )")
s2=$(perl -e "print( ('$d' . '$base' x $n ) x (2**($n-1)) )")
s3=$(perl -e "print( ('$d' . '$base' x (2**($n-1)) ) x ($n) )")

bash_sub < gawk

스트링 n #s #d 째려보다 b_sub b_internals. b_internals.
s1 10 229377 1 0.131 0.089 1.709 -
s1 10 229386 10 0.142 0.095 1.907 -
s2 8 32896 1 0.022 0.007 0.148 0.168
s2 8 34048 10 0.021 0.021 0.163 0.179
s3 12 786444 1 0.436 0.468 - -
s3 12 786456 2 0.434 0.317 - -
s3 12 786552 10 0.438 0.333 - -

bash_sub < 0.5초

스트링 n #s #d 째려보다 b_sub b_internals. b_internals.
s1 11 458753 1 0.256 0.332 (7.089) -
s1 11 458762 10 0.269 0.387 (8.003) -
s2 11 361472 1 0.205 0.283 (14.54) -
s2 11 363520 3 0.207 0.462 (16.66) -
s3 12 786444 1 0.436 0.468 - -
s3 12 786456 2 0.434 0.317 - -
s3 12 786552 10 0.438 0.333 - -

gawk < 0.5s

스트링 n #s d달러 째려보다 b_sub b_internals. b_internals.
s1 11 458753 1 0.256 0.332 (7.089) -
s1 11 458762 10 0.269 0.387 (8.003) -
s2 12 788480 1 0.440 (1.252) - -
s2 12 806912 10 0.449 (4.968) - -
s3 12 786444 1 0.436 0.468 - -
s3 12 786456 2 0.434 0.317 - -
s3 12 786552 10 0.438 0.333 - -

(s>160k, d=1의 bash_sub가 s3의 경우 d>1보다 느린 이유를 완전히 알 수 없습니다.)

모든 테스트는 xubuntu 20.04를 실행하고 있는 인텔 i7-7500U에서 bash 5.0.17을 사용하여 실시되었습니다.

enter code here순수 bash 다중 문자 딜리미터 솔루션.

이 스레드에서 지적된 바와 같이 OP의 질문에는 콤마 구분 문자열이 배열로 해석되는 예가 제시되어 있지만 콤마 구분자, 단일 문자 구분자 또는 다중 문자 구분자 중 어느 쪽에만 관심이 있는지 여부는 나타나지 않았습니다.

구글은 검색 결과의 상위 또는 그 부근에 이 답을 매기는 경향이 있기 때문에, 적어도 하나의 응답에서도 언급되고 있기 때문에, 저는 여러 개의 문자 구분자에 대한 질문에 대해 독자들에게 강력한 답을 제공하고 싶었습니다.

다중 문자 딜리미터 문제에 대한 해결책을 찾고 있다면, Mallikarjun M의 게시물, 특히 파라미터 확장을 사용하여 우아한 순수 BASH 솔루션을 제공하는 gniourf_gniourf의 응답을 검토할 것을 제안합니다.

#!/bin/bash
str="LearnABCtoABCSplitABCaABCString"
delimiter=ABC
s=$str$delimiter
array=();
while [[ $s ]]; do
    array+=( "${s%%"$delimiter"*}" );
    s=${s#*"$delimiter"};
done;
declare -p array

인용 코멘트/참조 투고에 대한 링크

인용된 질문 링크:bash에서 다중 문자 구분 기호로 문자열을 분할하려면 어떻게 해야 합니까?


업데이트 2022년 8월 3일

Xebeche는 아래의 코멘트에서 좋은 점을 제기했다.제안된 편집 내용을 검토한 후 gniourf_gniourf에서 제공하는 스크립트를 수정하고 스크립트가 무엇을 하고 있는지 알기 쉽게 설명을 추가했습니다.또한 많은 SHELL 베리에이션이 이중 괄호 표기법을 지원하지 않기 때문에 호환성을 높이기 위해 이중 괄호 [[]]를 단일 괄호로 변경했습니다.이 경우 BaaS의 경우 로직은 싱글브래킷 또는 더블브래킷 내에서 동작합니다.

#!/bin/bash
  
str="LearnABCtoABCSplitABCABCaABCStringABC"
delimiter="ABC"
array=()

while [ "$str" ]; do

    # parse next sub-string, left of next delimiter
    substring="${str%%"$delimiter"*}" 

    # when substring = delimiter, truncate leading delimiter
    # (i.e. pattern is "$delimiter$delimiter")
    [ -z "$substring" ] && str="${str#"$delimiter"}" && continue

    # create next array element with parsed substring
    array+=( "$substring" )

    # remaining string to the right of delimiter becomes next string to be evaluated
    str="${str:${#substring}}"

    # prevent infinite loop when last substring = delimiter
    [ "$str" == "$delimiter" ] && break

done

declare -p array

코멘트 없음:

#!/bin/bash
str="LearnABCtoABCSplitABCABCaABCStringABC"
delimiter="ABC"
array=()
while [ "$str" ]; do
    substring="${str%%"$delimiter"*}" 
    [ -z "$substring" ] && str="${str#"$delimiter"}" && continue
    array+=( "$substring" )
    str="${str:${#substring}}"
    [ "$str" == "$delimiter" ] && break
done
declare -p array

이거 드셔보세요

IFS=', '; array=(Paris, France, Europe)
for item in ${array[@]}; do echo $item; done

간단해요.필요에 따라서, 선언을 추가할 수도 있습니다(콤마도 삭제할 수도 있습니다).

IFS=' ';declare -a array=(Paris France Europe)

위의 작업을 취소하기 위해 IFS가 추가되지만 새로운 bash 인스턴스에서는 IFS 없이 동작합니다.

#!/bin/bash

string="a | b c"
pattern=' | '

# replaces pattern with newlines
splitted="$(sed "s/$pattern/\n/g" <<< "$string")"

# Reads lines and put them in array
readarray -t array2 <<< "$splitted"

# Prints number of elements
echo ${#array2[@]}
# Prints all elements
for a in "${array2[@]}"; do
        echo "> '$a'"
done

이 솔루션은 딜리미터가 큰 경우(2자 이상)에 사용할 수 있습니다.
하지 않습니다.

이것은, 지정된 데이터에 대해서 유효합니다.

$ aaa='Paris, France, Europe'
$ mapfile -td ',' aaaa < <(echo -n "${aaa//, /,}")
$ declare -p aaaa

결과:

declare -a aaaa=([0]="Paris" [1]="France" [2]="Europe")

또한 다음과 같이 공백이 있는 확장 데이터에도 사용할 수 있습니다.

$ aaa="New York, Paris, New Jersey, Hampshire"
$ mapfile -td ',' aaaa < <(echo -n "${aaa//, /,}")
$ declare -p aaaa

결과:

declare -a aaaa=([0]="New York" [1]="Paris" [2]="New Jersey" [3]="Hampshire")

IFS를 변경하지 않고 다음 방법으로 실행할 수 있습니다.

read -r -a myarray <<< "${string//, /$IFS}"

원하는 딜리미터와 일치하도록 IFS를 변경하는 것이 아니라 원하는 딜리미터의 모든 항목을 via 내용으로 대체할 수 있습니다.

아주 큰 현은 좀 느릴 것 같은데?

이것은 Dennis Williamson의 대답에 기초하고 있다.

word1, word2, ...와 같은 입력을 해석할 때 우연히 이 게시물을 발견했습니다.

위의 어떤 것도 나를 도와주지 않았다. awk를 사용해서 그것을 해결했다.도움이 되는 경우:

STRING="value1,value2,value3"
array=`echo $STRING | awk -F ',' '{ s = $1; for (i = 2; i <= NF; i++) s = s "\n"$i; print s; }'`
for word in ${array}
do
        echo "This is the word $word"
done

업데이트: 평가 문제가 있으므로 이 작업을 수행하지 마십시오.

조금 덜한 형식:

IFS=', ' eval 'array=($string)'

예.

string="foo, bar,baz"
IFS=', ' eval 'array=($string)'
echo ${array[1]} # -> bar

IFS를 변경하지 마십시오!

여기 간단한 bash 원라이너가 있습니다.

read -a my_array <<< $(echo ${INPUT_STRING} | tr -d ' ' | tr ',' ' ')

여기 내 해킹이 있다!

현을 현으로 나누는 것은 bash를 사용하는 것은 꽤 지루한 일입니다.그 결과, 몇개의 케이스(「;」, 「/」, 「」등)에서만 기능하는 어프로치가 한정되어 있거나, 출력에 다양한 부작용이 있습니다.

아래 방법에는 여러 가지 기동이 필요하지만, 대부분의 경우 효과가 있을 것으로 생각합니다.

#!/bin/bash

# --------------------------------------
# SPLIT FUNCTION
# ----------------

F_SPLIT_R=()
f_split() {
    : 'It does a "split" into a given string and returns an array.

    Args:
        TARGET_P (str): Target string to "split".
        DELIMITER_P (Optional[str]): Delimiter used to "split". If not 
    informed the split will be done by spaces.

    Returns:
        F_SPLIT_R (array): Array with the provided string separated by the 
    informed delimiter.
    '

    F_SPLIT_R=()
    TARGET_P=$1
    DELIMITER_P=$2
    if [ -z "$DELIMITER_P" ] ; then
        DELIMITER_P=" "
    fi

    REMOVE_N=1
    if [ "$DELIMITER_P" == "\n" ] ; then
        REMOVE_N=0
    fi

    # NOTE: This was the only parameter that has been a problem so far! 
    # By Questor
    # [Ref.: https://unix.stackexchange.com/a/390732/61742]
    if [ "$DELIMITER_P" == "./" ] ; then
        DELIMITER_P="[.]/"
    fi

    if [ ${REMOVE_N} -eq 1 ] ; then

        # NOTE: Due to bash limitations we have some problems getting the 
        # output of a split by awk inside an array and so we need to use 
        # "line break" (\n) to succeed. Seen this, we remove the line breaks 
        # momentarily afterwards we reintegrate them. The problem is that if 
        # there is a line break in the "string" informed, this line break will 
        # be lost, that is, it is erroneously removed in the output! 
        # By Questor
        TARGET_P=$(awk 'BEGIN {RS="dn"} {gsub("\n", "3F2C417D448C46918289218B7337FCAF"); printf $0}' <<< "${TARGET_P}")

    fi

    # NOTE: The replace of "\n" by "3F2C417D448C46918289218B7337FCAF" results 
    # in more occurrences of "3F2C417D448C46918289218B7337FCAF" than the 
    # amount of "\n" that there was originally in the string (one more 
    # occurrence at the end of the string)! We can not explain the reason for 
    # this side effect. The line below corrects this problem! By Questor
    TARGET_P=${TARGET_P%????????????????????????????????}

    SPLIT_NOW=$(awk -F"$DELIMITER_P" '{for(i=1; i<=NF; i++){printf "%s\n", $i}}' <<< "${TARGET_P}")

    while IFS= read -r LINE_NOW ; do
        if [ ${REMOVE_N} -eq 1 ] ; then

            # NOTE: We use "'" to prevent blank lines with no other characters 
            # in the sequence being erroneously removed! We do not know the 
            # reason for this side effect! By Questor
            LN_NOW_WITH_N=$(awk 'BEGIN {RS="dn"} {gsub("3F2C417D448C46918289218B7337FCAF", "\n"); printf $0}' <<< "'${LINE_NOW}'")

            # NOTE: We use the commands below to revert the intervention made 
            # immediately above! By Questor
            LN_NOW_WITH_N=${LN_NOW_WITH_N%?}
            LN_NOW_WITH_N=${LN_NOW_WITH_N#?}

            F_SPLIT_R+=("$LN_NOW_WITH_N")
        else
            F_SPLIT_R+=("$LINE_NOW")
        fi
    done <<< "$SPLIT_NOW"
}

# --------------------------------------
# HOW TO USE
# ----------------

STRING_TO_SPLIT="
 * How do I list all databases and tables using psql?

\"
sudo -u postgres /usr/pgsql-9.4/bin/psql -c \"\l\"
sudo -u postgres /usr/pgsql-9.4/bin/psql <DB_NAME> -c \"\dt\"
\"

\"
\list or \l: list all databases
\dt: list all tables in the current database
\"

[Ref.: https://dba.stackexchange.com/questions/1285/how-do-i-list-all-databases-and-tables-using-psql]


"

f_split "$STRING_TO_SPLIT" "bin/psql -c"

# --------------------------------------
# OUTPUT AND TEST
# ----------------

ARR_LENGTH=${#F_SPLIT_R[*]}
for (( i=0; i<=$(( $ARR_LENGTH -1 )); i++ )) ; do
    echo " > -----------------------------------------"
    echo "${F_SPLIT_R[$i]}"
    echo " < -----------------------------------------"
done

if [ "$STRING_TO_SPLIT" == "${F_SPLIT_R[0]}bin/psql -c${F_SPLIT_R[1]}" ] ; then
    echo " > -----------------------------------------"
    echo "The strings are the same!"
    echo " < -----------------------------------------"
fi

멀티라인 요소의 경우 다음과 같은 기능이 있습니다.

$ array=($(echo -e $'a a\nb b' | tr ' ' '§')) && array=("${array[@]//§/ }") && echo "${array[@]/%/ INTERELEMENT}"

a a INTERELEMENT b b INTERELEMENT

이 문제를 해결할 수 있는 방법은 매우 많기 때문에 먼저 솔루션에서 보고 싶은 것을 정의합니다.

  1. Bash를 합니다.readarray이 목적을 위해.★★★★★★ 。
  2. 변경 등 추악하고 불필요한 트릭을 피한다.IFS,, " ", "eval이치
  3. 유사한 문제에 쉽게 적응할 수 있는 간단하고 읽기 쉬운 방법을 찾아보세요.

readarray명령어는 딜리미터로 줄 바꿈과 함께 가장 쉽게 사용할 수 있습니다.다른 딜리미터를 사용하면 어레이에 요소를 추가할 수 있습니다.은 먼저 을 잘 맞는 입니다.readarray전달하기 전에.

이 예의 입력에는 다중 문자 구분 기호가 없습니다.약간의 상식을 적용하면 각 요소를 잘라내야 하는 쉼표로 구분된 입력으로 가장 잘 이해할 수 있습니다.이 솔루션은 콤마로 입력을 여러 줄로 분할하여 각 요소를 트리밍한 후 모든 것을 에 전달하는 것입니다.readarray.

string='  Paris,France  ,   All of Europe  '
readarray -t foo < <(tr ',' '\n' <<< "$string" |sed 's/^ *//' |sed 's/ *$//')

# Result:
declare -p foo
# declare -a foo='([0]="Paris" [1]="France" [2]="All of Europe")'

편집: 이 솔루션에서는 콤마 구분자 주위에 일관성이 없는 간격을 두고 요소를 공백으로 만들 수 있습니다.이러한 특수한 경우를 처리할 수 있는 솔루션은 거의 없습니다.

또한 추가 어레이 요소를 만든 후 제거하는 등 해킹처럼 보이는 접근 방식은 피합니다.이 답변에 동의하지 않으시면 댓글로 설명해 주세요.

Bash에서만 동일한 방법을 시도하고 하위 쉘 수를 줄이려는 경우 가능합니다.그러나 결과는 읽기 어려우며, 이러한 최적화는 불필요할 수 있습니다.

string='     Paris,France  ,   All of Europe    '
foo="${string#"${string%%[![:space:]]*}"}"
foo="${foo%"${foo##*[![:space:]]}"}"
foo="${foo//+([[:space:]]),/,}"
foo="${foo//,+([[:space:]])/,}"
readarray -t foo < <(echo "$foo")

또 다른 방법은 다음과 같습니다.

string="Paris, France, Europe"
IFS=', ' arr=(${string})

이제 요소가 "arr" 배열에 저장됩니다.요소를 반복하려면:

for i in ${arr[@]}; do echo $i; done

또 다른 접근방식은 다음과 같습니다.

str="a, b, c, d"  # assuming there is a space after ',' as in Q
arr=(${str//,/})  # delete all occurrences of ','

이 뒤에 'arr'은 4개의 문자열로 이루어진 배열입니다.따라서 IFS나 읽기, 기타 특별한 사항을 다룰 필요가 없으므로 훨씬 간단하고 직접적입니다.

언급URL : https://stackoverflow.com/questions/10586153/how-to-split-a-string-into-an-array-in-bash

반응형