Bash의 구분 기호에서 문자열을 어떻게 분할합니까?


질문

 

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

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

이제 문자열을 분리하고 싶습니다.내가 가지고있는 구분 기호 :

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

나는 반드시 addr1 및 addr2 변수가 필요하지 않습니다.배열의 요소 인 경우 훨씬 더 좋습니다.


아래 답변에서 제안한 후, 나는 다음과 같은 것으로 끝났습니다.

#!/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]

내부 필드 분리기 (IFS)를 설정하는 솔루션이있었습니다.그 답변에 무슨 일이 일어 났는지 모르겠지만, 어떻게되면 어떻게 되돌아 갔을까요?

Re : 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)

루프에서 인쇄 할 때만 첫 번째 문자열이 있습니다.


답변

 

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

IN="bla@some.com;john@home.com"
arrIN=(${IN//;/ })
echo ${arrIN[1]}                  # Output: john@home.com

설명:

이 구성은 모든 발생의 ';'(초기 //는 전역 대체를 의미합니다) ''(단일 공백)의 문자열에서 공간 구분 된 문자열을 배열로 해석합니다 (이는 주변 괄호가 수행하는 것)입니다.

곱슬 중괄호 안에서 사용 된 구문은 각 ';'를 대체합니다.문자가있는 문자를 매개 변수 확장이라고합니다.

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

  1. If the original string has spaces, you will need to use IFS:

ifs = ':';arrin = ($ in);ifs가 설정되지 않았습니다.

  1. If the original string has spaces and the delimiter is a new line, you can set IFS with:

IFS = $ '\ n';arrin = ($ in);ifs가 설정되지 않았습니다.



답변

내부 필드 구분 기호 (IFS) 변수를 설정 한 다음 배열로 구문 분석 할 수 있습니다.이 경우 명령에서 발생하면 IFS에 대한 할당은 해당 단일 명령의 환경 만 수행합니다 (읽으려면).그런 다음 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"


답변

Cut 명령을 참조하는 몇 가지 답변을 보았지만 모두 삭제되었습니다.이런 종류의 일을 할 때 더 많은 유용한 명령 중 하나가 특히 구분 된 로그 파일을 구문 분석하는 것이 좋습니다.

이 특정 예를 Bash 스크립트 배열로 분할하는 경우 TR은 아마도 효율적이지만 절단을 사용할 수 있으며 중간에서 특정 필드를 가져 오는 경우보다 효과적입니다.

예시:

$ 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

컷은이 파일을 고양이 할 수 있고 추가 처리를 위해 특정 필드를 선택하는 데 매우 편리합니다.



답변

호환되는 답변

bash 에서이 작업을 수행하는 데는 많은 여러 가지가 있습니다.

그러나 Bash는 다른 쉘에서 작동하지 않는 많은 특별한 특징 (소위 Bashisms)이 많이 있음을 주목하는 것이 중요합니다.

특히,이 게시물의 솔루션에뿐만 아니라 실내의 다른 솔루션에 사용되는 배열, 연관 배열 및 패턴 대체는 Bashisms이며 많은 사람들이 사용하는 다른 껍질에서는 작동하지 않을 수 있습니다.

예를 들어 : My Debian GNU / Linux에서는 표준 셸이라는 표준 쉘이 있습니다.나는 KSH라는 다른 껍질을 사용하기를 좋아하는 많은 사람들을 알고 있습니다.또한 자신의 쉘 인터프리터 (애쉬)로 바쁜 상자라는 특별한 도구가 있습니다.

요청한 문자열

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

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

이 문자열의 수정 된 버전을 사용하여 솔루션이 공백을 포함하는 문자열에 강력하도록하여 다른 솔루션을 깨뜨릴 수 있습니다.

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

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

순수한 bash에서는 IFS (입력 필드 구분 기호)에 대한 임시 값으로 분할 된 요소가있는 배열을 만들 수 있습니다.ifs 중에서, 배열을 정의 할 때 요소 간의 구분 기호로 취급 해야하는 캐릭터 (들)를보아야한다고 말합니다.

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'

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

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>")

포크 나 외부 리소스가 없기 때문에 읽기가 스플릿을 수행하는 가장 빠른 방법입니다.

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

# `"${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>]

업데이트 : 최근 bash> = 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를 사용할 수 없거나 많은 다른 쉘에서 사용할 수있는 것을 쓸 수있는 경우 종종 bashisms를 사용할 수 없으며이 솔루션에서 우리가 사용해온 배열이 포함됩니다.

그러나 열의 배열을 사용하여 문자열의 "요소"를 반복 할 필요가 없습니다.패턴의 첫 번째 또는 마지막 어커런스에서 문자열의 하위 문자열을 삭제하기위한 많은 쉘에 사용되는 구문이 있습니다.*는 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에서 설명한대로 :

#과 %는 각각 문자열의 시작과 끝에서 가능한 가장 짧은 일치하는 하위 문자열을 삭제하고 ## 및 %% 가장 긴 일치하는 하위 문자열을 삭제하십시오.

위의 구문을 사용하여 구분 기호 또는 이후에 하위 문자열을 삭제하여 문자열에서 하위 문자열 "요소를 추출하는 방법을 작성할 수 있습니다.

아래의 코드 블록은 Bash (Mac OS의 bash), 대시, ksh 및 busybox ssh에서 잘 작동합니다.

(Adam Katz의 코멘트 덕분 에이 루프가 훨씬 더 간단 해집니다!)

IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
while [ "$IN" != "$iter" ] ;do
    # extract the substring from start of string up to delimiter.
    iter=${IN%%;*}
    # delete this first "element" AND next separator, from $IN.
    IN="${IN#$iter;}"
    # Print (or doing anything with) the first "element".
    echo "> [$iter]"
done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]

재미있어!



답변

즉시 처리를 꺼리지 않으면 다음을 수행하고 싶습니다.

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

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



답변

나는 당신의 문제를 해결할 수있는 최선과 효율적인 명령이라고 생각합니다.AWK는 기본적으로 거의 모든 Linux 배포판에 포함되어 있습니다.

echo "bla@some.com;john@home.com" | awk -F';' '{print $1,$2}'

줄게

bla@some.com john@home.com

물론 AWK 인쇄 필드를 다시 정의하여 각 이메일 주소를 저장할 수 있습니다.

출처:https://stackoverflow.com/questions/918886/how-do-i-split-a-string-on-a-delimiter-in-bash