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]}"
Bash의 모든 버전에서 (2.05b 이후)
echo "${array[@]: -1:1}"
더 큰 음수 오프셋은 배열 끝에서 더 멀리 선택합니다. 이전 양식에서 빼기 기호 앞에 공백을 기록하십시오. 필수입니다.
답변
이 질문에 대한 모든 대답은 어떤 식 으로든 잘못되었습니다.
IFS=', ' read -r -a array <<< "$string"
1 : 의 오용입니다 $IFS
. 의 값은 $IFS
변수입니다 하지 A와 촬영 단일 가변 길이 오히려 그것이로한다 문자열 세퍼레이터 세트 의 단일 문자의 각 필드는 해당 문자열의 분리, read
입력 라인으로부터 벗어난 분열이 종료 될 수 있는 세트에서 문자 ( 이 예에서는 쉼표 또는 공백).
실제로, 실제 고수들에게는 전체 의미 $IFS
가 약간 더 관련되어 있습니다. 로부터 bash는 설명서 :
쉘은 IFS의 각 문자를 분리 문자로 취급 하고 이러한 문자를 필드 종결 자로 사용하여 다른 확장 결과를 단어로 나눕니다. 경우 IFS가 설정되지 않은 경우, 또는 그 값이 정확히 <스페이스> <탭> <개행 문자> , 기본의 다음 순서 <공간> , <탭> 및 <줄 바꿈> 시작과 이전 확장의 결과의 끝 무시 되고 시작 또는 끝에없는 IFS 문자 시퀀스는 단어를 구분하는 역할을합니다. IFS 에 기본값 이외의 값이 있으면 공백 문자 <space> , <tab> 및 <공백 문자가 IFS ( ISF 공백 문자) 값에있는 한 단어의 시작과 끝에서 무시됩니다 . 의 모든 문자 IFS 아닌 IFS 인접한과 함께 공백을 IFS , 필드을 구분 공백 문자. 일련의 IFS 공백 문자도 분리 문자로 처리됩니다. IFS 의 값 이 널이면 단어 분할이 발생하지 않습니다.
기본적으로 null이 아닌 값이 아닌 값의 $IFS
경우 필드는 (1) “IFS 공백 문자”집합 (즉, <space> 중 하나 이상)에서 하나 이상의 문자 시퀀스로 구분할 수 있습니다 . <tab> 및 <newline> ( 줄 바꿈 (LF)을 의미하는 “줄 바꿈” )은 $IFS
(2) 어디에나 존재 $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
다음, 변수가 발생 어떤 LFS를 포함 할 read
것 첫 번째 LF가 발생하면 처리를 중지하십시오. read
내장에만 호출 당 한 줄을 처리합니다. here-string 메커니즘 을 사용하여이 예제에서 수행하는 것처럼 입력 만 파이프 라인으로 보내거나 read
명령문으로 만 경로 재지 정하는 경우에도 마찬가지 이므로 처리되지 않은 입력은 유실됩니다. 내장 기능 을 강화하는 코드 에는 포함 된 명령 구조 내의 데이터 흐름에 대한 지식이 없습니다.read
이것이 문제를 일으킬 가능성은 없지만, 가능하면 피해야하는 미묘한 위험이라고 주장 할 수 있습니다. read
내장은 실제로 두 가지 수준의 입력 분할 을 수행하기 때문에 발생 합니다. OP는 한 수준의 분할 만 원 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]="")
아마도 OP는 이것에 신경 쓰지 않을지 모르지만 여전히 알아야 할 한계입니다. 솔루션의 견고성과 일반성을 줄입니다.
이 문제는 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> 및 하나 이상의 IFS 순서로 설정되어 있음을 암시합니다. 문자 (이제 모두 공백 문자 임)는 필드 구분 기호로 간주됩니다.
이것은 read
단어 분리 자체가 단 하나의 분리 레벨을 구성하기 때문에에 의해 커밋 된 두 레벨의 분리 문제를 해결합니다 . 그러나 이전과 마찬가지로 여기서 문제는 입력 문자열의 개별 필드에 이미 $IFS
문자 가 포함되어 있으므로 단어 분할 작업 중에 잘못 분할 될 수 있다는 것입니다. 이것은 이러한 응답자가 제공하는 샘플 입력 문자열 중 어느 경우에도 발생하지 않지만 (얼마나 편리합니다 …) 물론이 관용구를 사용한 코드베이스가 다음과 같은 위험을 초래한다는 사실을 변경하지는 않습니다. 이 가정이 어느 시점에서 선을 넘어 서면 폭파합니다. 다시 한번, 'Los Angeles, United States, North America'
(또는 'Los Angeles:United States:North America'
) 에 대한 나의 반례를 고려하십시오 .
또한, 단어 분할은 일반적으로 뒤 따른다 파일명 확장 ( 일명 패스 팽창 일명 , 완료되면, 문자를 포함하는 잠재적 손상 단어 것이다 로빙) *
, ?
또는 [
다음에 ]
(그리고 있다면, extglob
설정, 괄호 단편 앞에는 ?
, *
, +
, @
, 또는 !
) 파일 시스템 객체와 일치시키고 이에 따라 단어 ( “글로브”)를 확장합니다. 이 세 명의 응답자 중 첫 번째 응답자는 set -f
globbing을 비활성화하기 위해 미리 실행 하여이 문제를 영리하게 극복했습니다. 기술적으로 이것은 작동합니다 (아마도 추가해야하지만set +f
나중에 후속 코드에 대한 글 로빙을 다시 활성화해야합니다.)하지만 로컬 코드에서 기본 문자열 대 배열 구문 분석 작업을 해킹하기 위해 전역 셸 설정을 엉망으로 만드는 것은 바람직하지 않습니다.
이 답변의 또 다른 문제는 모든 빈 필드가 손실된다는 것입니다. 응용 프로그램에 따라 문제가 될 수도 있고 아닐 수도 있습니다.
참고 :이 솔루션을 사용하려는 경우 명령 대체 (쉘을 포크)를 호출하고 파이프 라인을 시작하는 데 어려움을 겪는 대신 ${string//:/ }
“패턴 대체”형식의 매개 변수 확장 을 사용하는 것이 좋습니다. 매개 변수 확장은 순전히 쉘 내부 조작이므로 외부 실행 파일 ( 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 와 거의 동일 합니다. 차이점은 응답자가 필드가 두 개의 문자로 구분되고 하나는 기본값으로 표시되고 $IFS
다른 하나는 그렇지 않은 것으로 가정한다는 것입니다 . 그는 패턴 대체 확장을 사용하여 비 IFS 표현 문자를 제거한 다음 단어 분할을 사용하여 존속하는 IFS 표현 분리 문자 문자에서 필드를 분할함으로써 다소 구체적인 경우를 해결했습니다.
이것은 매우 일반적인 해결책이 아닙니다. 또한 쉼표는 실제로 “기본”구분 문자이며, 필드 분리를위한 공백 문자에 따라이 문자를 스트리핑 한 다음 잘못했다고 주장 할 수 있습니다. 다시 한번, 내 반례를 고려하십시오 : 'Los Angeles, United States, North America'
.
또한 파일 이름 확장으로 확장 단어가 손상 될 수 있지만, set -f
및로 할당에 대한 globbing을 일시적으로 사용 중지하면이를 방지 할 수 있습니다 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와 같은 단일 문자 구분 기호의 경우 실제로는 완벽에 가깝습니다. 이전에 틀린 답으로 보았 듯이 필드가 실수로 중간에 나눌 수 없으며 필요에 따라 하나의 분리 수준 만 있습니다.
하나의 문제는 다시 한 번이에 중요한 문을 포장에 의해 해결 될 수 있지만 파일 이름 확장은, 이전의 손상 영향을받는 단어 설명 것 같은 것입니다 set -f
및 set +f
.
또 다른 잠재적 인 문제는 LF가 앞에서 정의한 “IFS 공백 문자”로 규정되기 때문에 # 2 및 # 3에서 와 같이 모든 빈 필드가 손실된다는 것 입니다. 구분자가 “IFS 공백 문자”가 아닌 경우에는 문제가되지 않으며 응용 프로그램에 따라 문제가되지 않을 수도 있지만 솔루션의 일반성을 저해합니다.
그래서, 당신은 하나의 문자 구분 기호를 가지고 가정, 요약하고,이 중 비 “공백 문자 IFS”또는 당신은 빈 필드에 대한 상관 없어, 당신은에 중요한 문을 포장 set -f
하고 set +f
,이 솔루션 작품 그렇지 않으면 그렇지 않습니다.
(또한 정보를 위해 bash의 변수에 LF를 할당하는 것은 $'...'
구문 과 같이 더 쉽게 수행 할 수 있습니다 IFS=$'\n';
.
countries='Paris, France, Europe'
OIFS="$IFS"
IFS=', ' array=($countries)
IFS="$OIFS"
IFS=', ' eval 'array=($string)'
이 솔루션은 효과적으로 # 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
있으므로 첫 번째 변형에서 변수 로 수행되는 것과 같은 전체 저장 및 복원 bit 비트를 피할 수 있습니다 . 그러나 여기서 직면 한 문제는 실행해야하는 명령 자체가 단순한 변수 할당이므로 $IFS
할당을 임시 로 만드는 명령 단어가 포함되지 않는다는 것 입니다. 당신은 자신에게 생각할 수도 있습니다. 왜 할당을 일시적으로 : builtin
만들기 위해 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에는 배열로 구문 분석해야하는 문자열 변수가 있습니다. 이 “답변”은 배열 리터럴에 붙여 넣은 입력 문자열의 완전 내용으로 시작합니다. 나는 그것이 한 가지 방법이라고 생각합니다.
응답자가 $IFS
변수가 모든 컨텍스트의 모든 bash 구문 분석에 영향을 미친 다고 가정했을 수도 있습니다 . bash 매뉴얼에서 :
IFS 확장 후 단어 분리 및 read 내장 명령을 사용하여 행을 단어로 분할하는 데 사용되는 내부 필드 구분 기호입니다 . 기본값은 <space> <tab> <newline> 입니다.
따라서 $IFS
특수 변수는 실제로 두 가지 컨텍스트에서만 사용됩니다. (1) 확장 후 수행되는 단어 분리 ( bash 소스 코드를 구문 분석 할 때가 아님 ) 및 (2) 입력 행을 read
내장 단어로 단어로 분할하는 경우 .
좀 더 명확하게 해보도록하겠습니다. 파싱 과 실행을 구분하는 것이 좋을 것이라고 생각합니다 . Bash는 먼저 소스 코드를 구문 분석 해야합니다 . 이는 명백하게 구문 분석 이벤트이며, 나중에 코드가 확장 될 때 코드가 실행 됩니다. 확장은 실제로 실행 이벤트입니다. 또한 $IFS
방금 인용 한 변수에 대한 설명과 관련하여 문제가 있습니다 . 오히려 단어 분할이 수행한다는보다 확장 후 , 나는 그 단어 분할이 수행되는 말을 하는 동안 아마도 더 정확하게, 단어 분할이며, 확장, 또는 의 일부확장 과정. “단어 분리”라는 문구는이 확장 단계만을 의미합니다. 불행히도 문서는 “split”과 “words”라는 단어를 많이 던지는 것처럼 보이지만 bash 소스 코드의 구문 분석을 참조하는 데 사용해서는 안됩니다. bash 매뉴얼 의 linux.die.net 버전 에서 발췌 한 내용은 다음과 같습니다 .
확장은 단어로 분할 된 후 명령 행에서 수행됩니다. 괄호 확장 , 틸드 확장 , 매개 변수 및 변수 확장 , 명령 대체 , 산술 확장 , 단어 분할 및 경로 이름 확장의 7 가지 확장이 수행 됩니다.
확장 순서는 다음과 같습니다. 괄호 확장; 물결표 확장, 매개 변수 및 변수 확장, 산술 확장 및 명령 대체 (왼쪽에서 오른쪽으로 수행); 단어 분할; 경로명 확장.
확장 섹션의 첫 문장에서 “단어”대신 “토큰”이라는 단어를 선택하기 때문에 GNU 버전 의 매뉴얼이 약간 더 나을 것이라고 주장 할 수 있습니다.
확장은 토큰으로 분할 된 후 명령 행에서 수행됩니다.
중요한 점은 $IFS
bash가 소스 코드를 구문 분석하는 방식을 변경하지 않는다는 것입니다. bash 소스 코드 구문 분석은 실제로 명령 시퀀스, 명령 목록, 파이프 라인, 매개 변수 확장, 산술 대체 및 명령 대체와 같은 셸 문법의 다양한 요소를 인식하는 매우 복잡한 프로세스입니다. 대부분의 경우 bash 파싱 프로세스는 변수 할당과 같은 사용자 수준 작업으로 변경할 수 없습니다 (실제로이 규칙에는 약간의 예외가 있습니다. 예를 들어 다양한 셸 설정 참조)compatxx
파싱 동작의 특정 측면을 즉시 변경할 수 있습니다). 이 복잡한 구문 분석 프로세스에서 발생하는 업스트림 “단어”/ “토큰”은 확장 된 (확장?) 텍스트의 단어를 다운 스트림으로 분할하는 위의 발췌 부분에서 분류 된 “확장”의 일반적인 프로세스에 따라 확장됩니다. 단어는 단순히 그 과정의 한 단계입니다. 단어 분리는 이전 확장 단계에서 뱉어 낸 텍스트 만 만집니다. 소스 바이트 스트림에서 바로 구문 분석 된 리터럴 텍스트에는 영향을 미치지 않습니다.
string='first line
second line
third line'
while read -r line; do lines+=("$line"); done <<<"$string"
이것은 최고의 솔루션 중 하나입니다. 을 다시 사용 read
합니다. read
우리가 하나만 필요할 때 두 가지 수준의 분리를 수행하기 때문에 부적절하다고 말하지 않았습니까 ? 여기서의 요점은 호출 read
당 하나의 필드 만 분리하여 효과적으로 한 수준의 분할 만 수행하는 방식으로 호출 할 수 있다는 것입니다. 루프에서 반복적으로 호출해야하는 비용이 필요합니다. 손이 약간 얇지 만 작동합니다.
그러나 문제가 있습니다. 첫 번째 :에 하나 이상의 NAME 인수를 제공 read
하면 입력 문자열에서 분리 된 각 필드의 선행 및 후행 공백이 자동으로 무시됩니다. 이 $IFS
게시물의 앞부분에서 설명한 것처럼 기본값으로 설정되어 있는지 여부에 관계없이 발생합니다 . 이제 OP는 특정 사용 사례에 대해 이것을 신경 쓰지 않을 수 있으며 실제로 구문 분석 동작의 바람직한 기능 일 수 있습니다. 그러나 문자열을 필드로 구문 분석하려는 모든 사람이 이것을 원하는 것은 아닙니다. 그러나 해결책이 있습니다. 다소 명확하지 않은 사용법은 NAME 인수를 read
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 루프에서 직접 수행 할 수도 있음). 그러나 또 다른 명백한 오류가 있습니다 : 유럽이 없습니다! 무슨 일이야? 대답은 read
최종 필드에서 최종 필드 종결자가 발생하지 않고 파일 끝 (이 경우 문자열 끝이라고 부름)에 도달하면 실패 리턴 코드 를 리턴하는 것입니다. 이로 인해 while 루프가 조기에 중단되고 최종 필드가 손실됩니다.
기술적으로 이와 동일한 오류가 이전 예제에도 영향을 미쳤습니다. 차이점은 필드 구분 기호가 LF로 설정되었다는 것입니다.이 -d
옵션 은 옵션을 지정하지 않을 때의 기본값 이며, <<<
( “here-string”) 메커니즘은 LF를 다음과 같이 공급하기 직전에 문자열에 자동으로 LF를 추가합니다. 명령에 입력하십시오. 따라서 이러한 경우 우연히 추가 더미 터미네이터를 입력에 추가하여 실수로 최종 필드가 떨어지는 문제를 해결했습니다. 이 솔루션을 “더미 터미네이터”솔루션이라고합니다. here-string에서 인스턴스화 할 때 입력 문자열과 직접 연결하여 사용자 정의 구분 기호에 대해 더미 종결 자 솔루션을 수동으로 적용 할 수 있습니다.
a=(); while read -rd,; do a+=("$REPLY"); done <<<"$string,"; declare -p a;
declare -a a=([0]="Paris" [1]=" France" [2]=" Europe")
문제가 해결되었습니다. 또 다른 해결책은 (1) read
리턴 된 실패와 (2) $REPLY
가 모두 비어있는 경우 while 루프를 중단하는 것 입니다. 즉, 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')
이 접근법은 또한 <<<
리디렉션 연산자에 의해 here-string에 자동으로 추가되는 비밀 LF를 보여줍니다 . 물론 앞에서 설명한 것처럼 명시적인 트리밍 작업을 통해 별도로 분리 할 수도 있지만, 수동 더미 터미네이터 방식은이를 직접 해결하므로 그대로 사용할 수 있습니다. 수동 더미 터미네이터 솔루션은 실제로이 두 가지 문제 (드롭-파이널 필드 문제 및 추가 된 LF 문제)를 한 번에 해결한다는 점에서 매우 편리합니다.
따라서 전반적으로 이것은 매우 강력한 솔루션입니다. 남아있는 약점은 다중 문자 구분 기호에 대한 지원이 부족하다는 것입니다. 나중에 다루겠습니다.
string='first line
second line
third line'
readarray -t lines <<<"$string"
(이것은 실제로 # 7 과 동일한 게시물에서 온 것으로 , 답변자는 같은 게시물에 두 가지 솔루션을 제공했습니다.)
의 readarray
동의어 인 내장 mapfile
이 이상적입니다. 한 번에 바이트 스트림을 배열 변수로 구문 분석하는 기본 제공 명령입니다. 루프, 조건부, 대체 또는 다른 것을 망칠 필요가 없습니다. 그리고 입력 문자열에서 공백을 제거하지 않습니다. ( -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')
우리는 그 결과가 # 7read
에서 논의 된 루핑 솔루션 의 이중 조건 접근법에서 얻은 결과와 동일하다는 것을 알 수 있습니다 . 우리는 수동 더미 터미네이터 트릭으로 이것을 거의 해결할 수 있습니다 .
readarray -td, a <<<"$string,"; declare -p a;
## declare -a a=([0]="Paris" [1]=" France" [2]=" Europe" [3]=$'\n')
여기서의 문제 readarray
는 <<<
리디렉션 연산자가 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에서 (zsh는 아니지만 우연히) 변수에 NUL 바이트를 포함 할 수 없기 때문입니다. 이 사전 처리 단계는 프로세스 대체에서 인라인으로 수행 될 수 있습니다. 다음은 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의 draconian 30,000 자 제한에 대해 공간이 부족하여 설명 할 수 없습니다. 나는 그것을 독자들을위한 연습으로 남겨 둘 것이다.
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}
$ substring의 모든 일치 항목을 공백으로 바꾼 다음 대체 된 문자열을 사용하여 배열을 초기화합니다.
(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 기능을 활용하면 모든 행을 인쇄하는 것이 매우 쉽습니다.
printf ">[%s]\n" "${lines[@]}"
>[first line]
>[ second line]
>[ third line]