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}"
음의 오프셋이 클수록 배열 끝에서 더 멀리 선택됩니다.이전 형식의 빼기 기호 앞에 공백이 있습니다.필수입니다.
이 질문에 대한 모든 답은 어떤 식으로든 틀렸다.
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) 「$IFS
IFS 공백 문자"가 입력 행에 표시됩니다.
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가 포함되어 있습니다.read
LF를 사용하다read
builtin은 호출당 한 줄만 처리합니다.이것은 입력의 파이핑 또는 리다이렉트만 하는 경우에도 해당됩니다.read
이 예에서 하고 있는 것과 같이 스테이트먼트는, 처리되지 않은 입력이 없어지는 것을 보증합니다.전원 코드read
builtin에는 포함된 명령 구조 내의 데이터 흐름에 대한 지식이 없습니다.
이것이 문제를 일으킬 가능성은 낮다고 주장할 수 있지만, 그래도 가능하면 피해야 할 미묘한 위험입니다.은 이 일이 일어나기 합니다.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
이치노
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
명령어를 사용하여 필드 값을 조작할 수 있습니다. ,,$(...)
`...`
를 사용하면 명령어 치환의 중첩이 심플해지고 텍스트에디터에 의한 구문 하이라이트가 향상되기 때문입니다).
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
.
또, 빈 필드는 모두 없어집니다.어플리케이션에 따라서는 문제가 될 수도 있고 문제가 되지 않을 수도 있습니다.
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';
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의 요구 사항에 대한 잘못된 대답입니다.
IFS=', '; array=(Paris, France, Europe)
IFS=' ';declare -a array=(Paris France Europe)
음... 뭐라고요?OP에는 배열로 구문 분석해야 하는 문자열 변수가 있습니다.이 "answer"는 입력 문자열의 문자 그대로를 배열 리터럴에 붙여넣는 것으로 시작합니다.그렇게 하는 것도 한 방법인 것 같아요.
.$IFS
가 아닙니다.bash 파싱은 bash 파싱에 영향을 줍니다.이것은 사실이 아닙니다.에서 : bash " " :
IFS 확장 후 단어 분할 및 read builtin 명령어로 행 분할에 사용되는 내부 필드 구분 기호.기본값은 <space> <tab> <newline> 입니다.
그...$IFS
special variable은 실제로 두 가지 컨텍스트에서만 사용됩니다. (1) 확장 후에 실행되는 단어 분할(bash 소스 코드를 해석할 때 아님) 및 (2) 입력 행을 에 의해 단어로 분할하기 위한 경우read
빌트인
이걸 좀 더 명확히 해 볼게요.파싱과 실행을 구분하는 것이 좋을 것 같습니다.Bash는 먼저 소스 코드를 구문 분석해야 합니다.이것은 명백히 구문 분석 이벤트입니다.그리고 나중에 코드를 실행합니다.이것은 확장이 그림으로 들어오는 경우입니다.확장은 실제로 실행 이벤트입니다.게다가, 나는 그 설명에 대해 이의를 제기한다.$IFS
위에서 인용한 변수입니다.단어 분할은 확장 후에 실행된다고 하기보다는 확장 중에 실행된다고 할 수 있습니다.또는 보다 정확하게는 단어 분할이 확장 프로세스의 일부라고 할 수 있습니다."word split"이라는 문구는 이 확장 단계만을 가리킵니다.bash 소스 코드의 해석을 가리킬 때는 사용하지 마십시오.다만, 유감스럽게도 문서는 "split"과 "words"라는 단어를 많이 사용하는 것 같습니다.다음은 linux.die에서 발췌한 내용입니다.bash 매뉴얼의 net 버전:
확장은 명령줄을 단어로 분할한 후 수행됩니다.수행되는 확장에는 브레이스 확장, 칠드 확장, 파라미터 및 변수 확장, 명령어 대체, 산술 확장, 단어 분할 및 경로 이름 확장의 7가지 종류가 있습니다.
확장 순서는 가새 확장, 칠드 확장, 파라미터 및 변수 확장, 산술 확장 및 명령어 대체(왼쪽에서 오른쪽으로 실행), 단어 분할 및 경로 이름 확장입니다.
설명서의 GNU 버전은 확장 섹션의 첫 번째 문장에서 "words" 대신 "tokens"라는 단어를 선택하기 때문에 약간 더 낫다고 주장할 수 있습니다.
토큰으로 분할된 후 명령줄에서 확장이 수행됩니다.
은 '아까운 것'입니다$IFS
bash가 소스 코드를 해석하는 방법은 변경되지 않습니다.bash 소스 코드의 해석은 명령어시퀀스, 명령어리스트, 파이프라인, 파라미터 확장, 산술 치환, 명령어 치환 등 셸 문법의 다양한 요소를 인식하는 매우 복잡한 프로세스입니다.대부분의 경우 변수 할당과 같은 사용자 수준의 액션으로는 bash 구문 분석 프로세스를 변경할 수 없습니다(실제로 이 규칙에는 몇 가지 사소한 예외가 있습니다.예를 들어 다양한 셸 설정을 참조해 주세요.이는 해석 동작의 특정 측면을 즉시 변경할 수 있습니다).이 복잡한 해석 프로세스에서 발생하는 업스트림 "words"/"token"은 위의 문서 발췌에서 설명한 바와 같이 일반적인 "expansion" 프로세스에 따라 전개됩니다.여기서 확장(확장?) 텍스트를 다운스트림 단어로 분할하는 것은 그 과정의 한 단계일 뿐입니다.단어 분할은 이전 확장 단계에서 뱉은 텍스트에만 영향을 줍니다. 원본 bytestream에서 바로 구문 분석된 리터럴 텍스트에는 영향을 주지 않습니다.
string='first line
second line
third line'
while read -r line; do lines+=("$line"); done <<<"$string"
이치 이제 을 살펴보도록 하겠습니다.read
'아까'라고 않았나요read
한 단계만 필요한 경우 두 가지 수준의 분할을 수행하기 때문에 적합하지 않은가?이란, 불러도 거죠.read
이러한 방법으로 효율적으로 한 가지 수준의 분할만 수행할 수 있으며, 특히 호출당 하나의 필드만 분할함으로써 루프에서 반복 호출해야 하는 비용이 발생합니다.★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
하지만 문제가 있다.첫 번째: 적어도1개의 NAME 인수를 지정한 경우read
입력 문자열에서 분리된 각 필드의 선행 및 후행 공백을 자동으로 무시합니다.이것은, 다음의 경우에 발생합니다.$IFS
는 이 투고에서 설명한 바와 같이 디폴트값으로 설정되어 있는지 여부를 나타냅니다.OP는 자신의 특정 사용 예에 대해 이 문제에 대해 신경 쓰지 않을 수 있으며, 실제로 이 기능은 구문 분석 동작의 바람직한 기능일 수 있습니다.그러나 문자열을 필드에 구문 분석하려는 모든 사용자가 원하는 것은 아닙니다.: 입니다. 하다read
name 인수를 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 문제)를 한 번에 해결한다는 점에서 매우 편리합니다.
따라서 전반적으로 이 솔루션은 매우 강력한 솔루션입니다.유일한 약점은 다중 문자의 구분자를 지원하지 않는다는 것입니다. 나중에 설명하겠습니다.
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')
에서 얻은 하다는 것을 알 수 .read
7번에서 설명한 솔루션.수동 더미 터미네이터 트릭으로 이 문제를 해결할 수 있습니다.
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
입니다.", "
를 사용한 .IFS
IFS는 문자열이 아니라 여러 문자의 집합이기 때문에 여러 문자의 딜리미터는 본질적으로 잘못되어 있습니다.
「 」를 할당했을 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 구현에 대해 간단한 벤치마크를 실시했습니다.
요약하면 다음과 같습니다.
- 문자열 길이가 4k 미만일 경우 순수 배시가 gawk보다 빠릅니다.
- 딜리미터 길이 < 10 및 문자열 길이 < 256k의 경우 순수 bash는 gawk와 동일합니다.
- 딜리미터 길이>> 10 및 스트링 길이< 64k의 경우 순수 bash는 "허용"이며 gawk는 5배 미만입니다.
- 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 )
}
번째 " " "-z
Naigive 버전의 test에서는 제로 길이의 문자열이 전달되는 경우를 처리합니다.테스트하지 않으면 출력 배열은 비어 있습니다.테스트를 사용하면 배열에는 단일 제로 길이 요소가 포함됩니다.
<고객명>의 치환readarray
while read
10%
사용한 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
이 문제를 해결할 수 있는 방법은 매우 많기 때문에 먼저 솔루션에서 보고 싶은 것을 정의합니다.
- Bash를 합니다.
readarray
이 목적을 위해.★★★★★★ 。 - 변경 등 추악하고 불필요한 트릭을 피한다.
IFS
,, " ", "eval
이치 - 유사한 문제에 쉽게 적응할 수 있는 간단하고 읽기 쉬운 방법을 찾아보세요.
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
'programing' 카테고리의 다른 글
Excel VBA - 2D 어레이 리딤 방법 (0) | 2023.04.12 |
---|---|
iOS - ViewController에서 앱 위임 메서드를 호출합니다. (0) | 2023.04.12 |
Bash에서 ${}와 $()의 차이 (0) | 2023.04.12 |
WPF XAML StringFormat DateTime:잘못된 문화에서 산출물? (0) | 2023.04.12 |
UICollor에서 약간 더 밝은 색상과 어두운 색상으로 구현 (0) | 2023.04.12 |