[bash] Bash에서 구분 기호로 문자열을 어떻게 분할합니까?

이 문자열을 변수에 저장했습니다.

IN="bla@some.com;john@home.com"

이제 문자열을 ;구분 기호 로 나누고 싶습니다 .

ADDR1="bla@some.com"
ADDR2="john@home.com"

반드시 ADDR1ADDR2변수 가 필요하지는 않습니다 . 그것들이 배열의 요소라면 더 좋습니다.


아래 답변의 제안 후, 나는 다음과 같은 결과를 얻었습니다.

#!/usr/bin/env bash

IN="bla@some.com;john@home.com"

mails=$(echo $IN | tr ";" "\n")

for addr in $mails
do
    echo "> [$addr]"
done

산출:

> [bla@some.com]
> [john@home.com]

Internal_field_separator (IFS)를로 설정하는 솔루션이있었습니다 ;. 그 대답에 무슨 일이 있었는지 잘 모르겠습니다. 어떻게 IFS기본값으로 재설정 합니까?

RE : IFS솔루션, 이것을 시도하고 작동하며 오래된 것을 유지 IFS한 다음 복원하십시오.

IN="bla@some.com;john@home.com"

OIFS=$IFS
IFS=';'
mails2=$IN
for x in $mails2
do
    echo "> [$x]"
done

IFS=$OIFS

BTW, 내가 시도했을 때

mails2=($IN)

루프에서 인쇄 할 때 첫 번째 문자열 만 얻었습니다 $IN. 괄호없이 작동합니다.



답변

내부 필드 구분 기호를 설정할 수 있습니다 (IFS) 변수, 그리고 그 배열로 분석하자. 이것이 명령에서 IFS발생하면 해당 단일 명령 환경에만 할당이 수행 됩니다 (to read). 그런 다음 IFS변수 값 에 따라 입력을 구문 분석하여 배열을 반복합니다.

IFS=';' read -ra ADDR <<< "$IN"
for i in "${ADDR[@]}"; do
    # process "$i"
done

로 구분 된 한 줄의 항목을 구문 분석 ;하여 배열로 밀어 넣습니다. $IN한 줄의 입력을 다음으로 구분할 때마다 전체를 처리하기위한 것 ;:

 while IFS=';' read -ra ADDR; do
      for i in "${ADDR[@]}"; do
          # process "$i"
      done
 done <<< "$IN"


답변

Bash 쉘 스크립트 분할 배열 에서 가져온 것 :

IN="bla@some.com;john@home.com"
arrIN=(${IN//;/ })

설명:

이 구성은 문자열에서 모든 발생 ';'(초기 //글로벌 대체) IN' ' (단일 공백)으로 바꾼 다음 공백으로 구분 된 문자열을 배열로 해석합니다 (주변 괄호의 기능).

';'문자를 문자 로 대체하기 위해 중괄호 안에 사용되는 구문을 매개 변수 확장' ' 이라고합니다. 합니다.

몇 가지 일반적인 문제가 있습니다.

  1. 원래 문자열에 공백이 있으면 IFS 를 사용해야합니다 .
    • IFS=':'; arrIN=($IN); unset IFS;
  2. 원래 문자열에 공백이 있고 분리 문자가 줄 바꾸기 인 경우 다음을 사용 하여 IFS 를 설정할 수 있습니다 .
    • IFS=$'\n'; arrIN=($IN); unset IFS;

답변

즉시 처리하는 것이 마음에 들지 않으면 다음과 같이하십시오.

for i in $(echo $IN | tr ";" "\n")
do
  # process
done

이런 종류의 루프를 사용하여 배열을 초기화 할 수는 있지만 더 쉬운 방법이 있습니다. 그래도 도움이되기를 바랍니다.


답변

호환되는 답변

이 작업을 수행하는 방법에는 여러 가지가 있습니다 .

그러나 다른 어떤 것도 작동하지 않는 bash많은 특수 기능 (소위 bashism )이 있다는 것을 먼저 알아야합니다.

특히이 게시물의 솔루션과 스레드의 다른 솔루션에서 사용되는 배열 , 연관 배열패턴 대체bashism 이며 많은 사람들이 사용하는 다른 에서는 작동하지 않을 수 있습니다.

예를 들어 , 데비안 GNU / 리눅스 에는 표준 쉘이 있습니다.; 나는 다른 쉘을 좋아하는 많은 사람들을 알고 있습니다.; 또한 특별한 도구가 있습니다 자신의 쉘 인터프리터 ().

요청 된 문자열

위의 질문에서 나눌 문자열은 다음과 같습니다.

IN="bla@some.com;john@home.com"

이 문자열의 수정 된 버전을 사용하여 솔루션이 공백이 포함 된 문자열에 강력 해 다른 솔루션을 손상시킬 수 있도록합니다.

IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"

구분 기호를 기준으로 분할 문자열 (버전> = 4.2)

pure 에서는 IFS ( 입력 필드 구분자 ) 에 대한 임시 값으로 요소를 분할 bash하여 배열 을 작성할 수 있습니다 . 무엇보다도 IFS 는 배열을 정의 할 때 어떤 문자를 요소 사이의 구분자로 취급해야하는지 알려줍니다 .bash

IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"

# save original IFS value so we can restore it later
oIFS="$IFS"
IFS=";"
declare -a fields=($IN)
IFS="$oIFS"
unset oIFS

의 최신 버전 bash에서 명령 앞에 IFS 정의를 추가하면 해당 명령에 대한 IFS 변경되고 바로 이전 값으로 재설정됩니다. 즉, 한 줄로 위의 작업을 수행 할 수 있습니다.

IFS=\; read -a fields <<<"$IN"
# after this command, the IFS resets back to its previous value (here, the default):
set | grep ^IFS=
# IFS=$' \t\n'

문자열 INfields세미콜론으로 분할 된 이라는 배열에 저장되었음을 알 수 있습니다 .

set | grep ^fields=\\\|^IN=
# fields=([0]="bla@some.com" [1]="john@home.com" [2]="Full Name <fulnam@other.org>")
# IN='bla@some.com;john@home.com;Full Name <fulnam@other.org>'

(우리는 또한 사용하여 이러한 변수의 내용을 표시 할 수 있습니다 declare -p:

declare -p IN fields
# declare -- IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
# declare -a fields=([0]="bla@some.com" [1]="john@home.com" [2]="Full Name <fulnam@other.org>")

그주의 read는 IS 빠른 전혀 없기 때문에 분할을 할 수있는 방법 포크 라는 외부 자원.

배열이 정의되면 간단한 루프를 사용하여 각 필드 (또는 이제 정의한 배열의 각 요소)를 처리 할 수 ​​있습니다.

# `"${fields[@]}"` expands to return every element of `fields` array as a separate argument
for x in "${fields[@]}" ;do
    echo "> [$x]"
    done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]

또는 시프트 방식을 사용하여 처리 한 후 배열에서 각 필드를 삭제할 수 있습니다 .

while [ "$fields" ] ;do
    echo "> [$fields]"
    # slice the array 
    fields=("${fields[@]:1}")
    done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]

배열의 간단한 인쇄물을 원한다면 반복 할 필요조차 없습니다.

printf "> [%s]\n" "${fields[@]}"
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]

업데이트 : 최근 > = 4.4

의 최신 버전에서는 bash다음 명령을 사용하여 재생할 수도 있습니다 mapfile.

mapfile -td \; fields < <(printf "%s\0" "$IN")

이 구문은 특수 문자, 줄 바꿈 및 빈 필드를 유지합니다!

빈 필드를 포함하지 않으려면 다음을 수행하십시오.

mapfile -td \; fields <<<"$IN"
fields=("${fields[@]%$'\n'}")   # drop '\n' added by '<<<'

를 사용하면 mapfile배열 선언을 건너 뛰고 구분 된 요소를 암시 적으로 “루프”하여 각 함수를 호출 할 수 있습니다.

myPubliMail() {
    printf "Seq: %6d: Sending mail to '%s'..." $1 "$2"
    # mail -s "This is not a spam..." "$2" </path/to/body
    printf "\e[3D, done.\n"
}

mapfile < <(printf "%s\0" "$IN") -td \; -c 1 -C myPubliMail

(참고 : \0문자열 끝의 빈 필드에 신경 쓰지 않거나 존재하지 않는 경우 형식 문자열의 끝에는 쓸모가 없습니다.)

mapfile < <(echo -n "$IN") -td \; -c 1 -C myPubliMail

# Seq:      0: Sending mail to 'bla@some.com', done.
# Seq:      1: Sending mail to 'john@home.com', done.
# Seq:      2: Sending mail to 'Full Name <fulnam@other.org>', done.

또는 을 사용할 수 <<<있으며 함수 본문에 추가 줄 바꿈을 삭제하는 처리가 포함됩니다.

myPubliMail() {
    local seq=$1 dest="${2%$'\n'}"
    printf "Seq: %6d: Sending mail to '%s'..." $seq "$dest"
    # mail -s "This is not a spam..." "$dest" </path/to/body
    printf "\e[3D, done.\n"
}

mapfile <<<"$IN" -td \; -c 1 -C myPubliMail

# Renders the same output:
# Seq:      0: Sending mail to 'bla@some.com', done.
# Seq:      1: Sending mail to 'john@home.com', done.
# Seq:      2: Sending mail to 'Full Name <fulnam@other.org>', done.

구분 기호를 기준으로 분할 문자열

을 사용할 수 없거나 bash많은 다른 쉘에서 사용할 수있는 것을 쓰려면 종종 bashism을 사용할 수 없으며 여기에는 위의 솔루션에서 사용한 배열이 포함됩니다.

그러나 문자열의 “요소”를 반복하기 위해 배열을 사용할 필요는 없습니다. 많은 쉘에서 문자열의 하위 문자열을 삭제하기 위해 사용되는 구문이 있습니다.패턴 처음 또는 마지막 항목 . 참고 *0 개 이상의 문자를 의미 와일드 카드입니다 :

(지금까지 게시 된 솔루션 에이 접근법이 없기 때문에이 답변을 작성하는 주된 이유입니다.)

${var#*SubStr}  # drops substring from start of string up to first occurrence of `SubStr`
${var##*SubStr} # drops substring from start of string up to last occurrence of `SubStr`
${var%SubStr*}  # drops substring from last occurrence of `SubStr` to end of string
${var%%SubStr*} # drops substring from first occurrence of `SubStr` to end of string

설명대로 Score_Under에서 :

#%삭제 최단 매칭로부터 서브 스트링 시작끝에 각각의 문자열을, 및

##%% 가능한 가장 긴 일치하는 하위 문자열을 삭제합니다.

위의 구문을 사용하면 구분 기호까지 또는 이후에 하위 문자열을 삭제하여 문자열에서 하위 문자열 “요소”를 추출하는 방법을 만들 수 있습니다.

아래 코드 블록은 잘 작동합니다 (Mac OS 포함 bash ), , ‘에스 :

IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
while [ "$IN" ] ;do
    # extract the substring from start of string up to delimiter.
    # this is the first "element" of the string.
    iter=${IN%%;*}
    echo "> [$iter]"
    # if there's only one element left, set `IN` to an empty string.
    # this causes us to exit this `while` loop.
    # else, we delete the first "element" of the string from IN, and move onto the next.
    [ "$IN" = "$iter" ] && \
        IN='' || \
        IN="${IN#*;}"
  done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]

즐기세요!


답변

cut명령을 참조하는 몇 가지 답변을 보았지만 모두 삭제되었습니다. 이 유형의 작업, 특히 구분 된 로그 파일을 구문 분석하는 데 유용한 명령 중 하나라고 생각하기 때문에 아무도 그것에 대해 자세히 설명하지 않은 것이 조금 이상합니다.

이 특정 예제를 bash 스크립트 배열로 분할하는 경우 tr더 효율적이지만 cut사용할 수 있으며 중간에서 특정 필드를 가져 오려는 경우 더 효과적입니다.

예:

$ echo "bla@some.com;john@home.com" | cut -d ";" -f 1
bla@some.com
$ echo "bla@some.com;john@home.com" | cut -d ";" -f 2
john@home.com

분명히 루프에 넣고 -f 매개 변수를 반복하여 각 필드를 독립적으로 가져옵니다.

다음과 같이 행이있는 구분 된 로그 파일이있는 경우 더 유용합니다.

2015-04-27|12345|some action|an attribute|meta data

cut 할 수있어 매우 편리합니다 cat이 파일을 사용하고 추가 처리를 위해 특정 필드를 선택할 .


답변

이것은 나를 위해 일했다 :

string="1;2"
echo $string | cut -d';' -f1 # output is 1
echo $string | cut -d';' -f2 # output is 2


답변

이 방법은 어떻습니까?

IN="bla@some.com;john@home.com"
set -- "$IN"
IFS=";"; declare -a Array=($*)
echo "${Array[@]}"
echo "${Array[0]}"
echo "${Array[1]}" 

출처