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


2041

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

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. 괄호없이 작동합니다.


14
"Edit2"와 관련하여 : "FSF 설정 해제"만하면 기본 상태로 돌아갑니다. 이미 기본값이 아닌 값으로 설정되어 있다고 예상 할만한 이유가 없으면 명시 적으로 저장하고 복원 할 필요가 없습니다. 또한 함수 내 에서이 작업을 수행하는 경우 (그렇지 않은 경우 왜 그렇지 않습니까?) IFS를 로컬 변수로 설정하면 함수를 종료하면 이전 값으로 돌아갑니다.
Brooks Moses

19
@BrooksMoses : (a) local IFS=...가능한 경우 +1 ; (b) -1에 대해 unset IFS, 이것은 IFS를 기본값으로 정확하게 재설정하지는 않지만 설정되지 않은 IFS는 IFS의 기본값 ($ '\ t \ n')과 동일하게 작동하지만, 나쁜 습관으로 보입니다. IFS가 사용자 정의 값으로 설정된 상태에서 코드가 호출되지 않는다고 맹목적으로 가정하십시오. (c) 또 다른 아이디어는 서브 쉘을 호출하는 것입니다. 서브 (IFS=$custom; ...)쉘이 종료되면 IFS는 원래 상태로 돌아갑니다.
dubiousjim

실행 파일을 던질 위치를 결정하는 경로를 간단히 살펴보고 싶었습니다 ruby -e "puts ENV.fetch('PATH').split(':')". 순수한 bash를 유지하려면 도움이되지 않지만 내장 분할 기능이 있는 스크립팅 언어 를 사용 하는 것이 더 쉽습니다.
nicooga

4
for x in $(IFS=';';echo $IN); do echo "> [$x]"; done
user2037659

2
그것을 배열로 저장하기 위해 또 다른 괄호 세트를 배치 \n하고 공백으로 바꿔야했습니다 . 마지막 줄은 mails=($(echo $IN | tr ";" " "))입니다. 이제 mails배열 표기법을 사용 mails[index]하거나 루프에서 반복 하여 요소를 확인할 수 있습니다.
afranques

답변:


1234

내부 필드 구분 기호를 설정할 수 있습니다 (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"

22
아마도 가장 좋은 방법 일 것입니다. IFS는 현재 값으로 얼마나 오래 유지되며, 설정하지 않아야 할 코드를 설정하여 코드를 엉망으로 만들 수 있으며, 완료되면 어떻게 재설정 할 수 있습니까?
Chris Lutz

7
이제 수정 프로그램을 적용한 후 만 읽기 명령 :)의 지속 시간
litb - 요하네스 SCHAUB

14
while 루프를 사용하지 않고 모든 것을 한 번에 읽을 수 있습니다. read -r -d ''-a addr <<< "$ in"# -d ''는 여기에 핵심이며, 첫 번째 줄 바꿈에서 멈추지 않도록 읽습니다 ( 이는 기본 -d)이지만 EOF 또는 NULL 바이트 (2 진 데이터에서만 발생)까지 계속됩니다.
lhunath

55
@LucaBorrione 별도의 명령이 아닌 세미콜론이나 다른 구분 기호가없는 IFS것과 동일한 행에 설정 read하면 해당 명령의 범위가 지정되므로 항상 "복원"됩니다. 수동으로 아무것도 할 필요가 없습니다.
찰스 더피

5
@imagineerThis herestrings 및 $IN인용 해야 할 IFS의 로컬 변경과 관련된 버그 가 있습니다. 버그는 bash4.3 에서 수정되었습니다 .
chepner

973

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;

84
난 그냥 추가하고 싶습니다 : 이것은 가장 간단합니다, 당신은 $ {arrIN [1]} (물론 0부터 시작)으로 배열 요소에 접근 할 수 있습니다
Oz123

26
그것을 발견 : $ {} 내에서 변수를 수정하는 기술을 '매개 변수 확장'이라고합니다.
KomodoDave

23
아니요, 공백이있을 때 이것이 효과가 있다고 생각하지 않습니다 ... ','를 ''로 변환 한 다음 공백으로 구분 된 배열을 작성합니다.
Ethan

12
매우 간결하지만 일반적인 사용에 대한주의 사항이 있습니다 . 셸 은 문자열에 단어 분리확장 을 적용 합니다. 그냥 사용해보십시오. IN="bla@some.com;john@home.com;*;broken apart". 요컨대, 토큰에 공백과 문자가 포함되어 있으면이 방법이 중단됩니다. 등 *이 현재 폴더에 토큰 일치하는 파일 이름을 만드는 일.
mklement0

53
이것은 다른 이유에 대한 나쁜 방법이다 : 당신의 문자열이 포함 된 경우 예를 들어, ;*;다음은 *현재 디렉토리에있는 파일 이름 목록으로 확장됩니다. -1
Charles Duffy

249

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

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

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


IFS 답변을 유지해야합니다. 그것은 내가 모르는 것을 가르쳐 주었고, 확실히 배열을 만들었지 만, 이것은 값싼 대체물을 만듭니다.
Chris Lutz

내가 참조. 그래, 나는이 어리석은 실험을하고있다. 나는 대답하려고 할 때마다 새로운 것을 배울 것이다. #bash IRC 피드백을 기반으로 내용을 편집하고 삭제되지 않은 :)
Johannes Schaub-litb

33
-1, 코드에 두 가지 버그가 있기 때문에 분명히 단어 분할에 대해 잘 모릅니다. 하나는 $ IN을 인용하지 않는 것이고 다른 하나는 줄 바꿈이 단어 분할에 사용되는 유일한 구분자 인 경우입니다. 모든 줄이 아닌 IN의 모든 단어를 반복하고 세미콜론으로 구분 된 모든 요소를 ​​확실하게 정의하지는 않지만 작동하는 것처럼 보이는 부작용이있는 것처럼 보일 수 있습니다.
lhunath

3
"$ IN"을 반향하도록 변경할 수 있습니다. | tr ';' '\ n'| -r ADDY를 읽는 동안; # 처리 "$ ADDY"; 그 운 수 있도록하기위한 것, 내가 생각 :)이 포크 것입니다, 당신은 루프 내에서 외부 변수를 변경할 수 없습니다 (즉, 내가 <<< "$ IN"구문을 사용하는 이유의) 다음
요하네스 SCHAUB - litb

8
주석의 토론을 요약하려면 : 일반적인 사용에 대한주의 사항 : 쉘은 단어 분리확장 을 문자열에 적용합니다. 그냥 사용해보십시오. IN="bla@some.com;john@home.com;*;broken apart". 요컨대, 토큰에 공백과 문자가 포함되어 있으면이 방법이 중단됩니다. 등 *이 현재 폴더에 토큰 일치하는 파일 이름을 만드는 일.
mklement0

202

호환되는 답변

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

그러나 다른 어떤 것도 작동하지 않는 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>]

즐기세요!


15
#, ##, %, 및 %%치환 (그들은 삭제 얼마나 많은에 대한) 기억하기 쉬운 설명이 IMO 무엇 있습니다 #% 최단 일치하는 문자열을 삭제하고, ##그리고 %%가장 긴 삭제합니다.
Score_Under

1
IFS=\; read -a fields <<<"$var"줄 바꿈에 실패하고 뒤에 줄 바꿈을 추가합니다. 다른 솔루션은 후행 빈 필드를 제거합니다.
Isaac Isaac

쉘 구분 기호는 가장 우아한 대답, 기간입니다.
에릭 첸

마지막 대안을 다른 곳에 설정된 필드 구분 기호 목록과 함께 사용할 수 있습니까? 예를 들어, 이것을 셸 스크립트로 사용하고 필드 구분 기호 목록을 위치 매개 변수로 전달합니다.
sancho.s ReinstateMonicaCellio

예, 루프 :for sep in "#" "ł" "@" ; do ... var="${var#*$sep}" ...
F. Hauri

184

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이 파일을 사용하고 추가 처리를 위해 특정 필드를 선택할 .


6
를 사용 cut하는 것이 좋습니다. 이 작업에 적합한 도구입니다! 쉘 핵 해킹보다 훨씬 더 많은 것을 제거했다.
MisterMiyagi

4
이 방법은 요소 수를 미리 알고있는 경우에만 작동합니다. 더 많은 로직을 프로그래밍해야합니다. 또한 모든 요소에 대해 외부 도구를 실행합니다.
uli42

전적으로 csv에서 빈 문자열을 피하려고했습니다. 이제 정확한 '열'값도 가리킬 수 있습니다. 루프에서 이미 사용 된 IFS 작업 내 상황에서 예상보다 낫습니다.
Louis Loudog Trottier

너무 즉 ID와 PID를 당겨 매우 유용
밀로스 Grujic을

이 답변은 페이지 절반 이상 아래로 스크롤 할 가치가 있습니다. :
Gucu112

124

이것은 나를 위해 일했다 :

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

1
단일 문자 구분 기호로만 작동하지만 OP가 찾고있는 것입니다 (세미콜론으로 구분 된 레코드).
GuyPaddock

약 4 년 전에 @Ashok 에 의해 , 그리고 1 년 전에 @DougW 에 의해 답변보다 더 많은 정보가 제공됩니다. 다른 솔루션과 다른 솔루션을 게시하십시오.
MAChitgarha

90

이 방법은 어떻습니까?

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

출처


7
+1 ...하지만 변수 이름을 "Array"로 지정하지 않을 것입니다. 좋은 해결책입니다.
이즈미르 라미레즈

14
+1 ... 그러나 "set"과 선언 -a는 불필요합니다. 당신은 또한 그냥 사용할 수 있습니다IFS";" && Array=($IN)
ata

+1 참고 사항 : 기존 IFS를 유지 한 다음 복원하는 것이 좋지 않습니까? (stefanB가 그의 편집 3에서 보여준 바와 같이) 여기에 착륙 한 사람들 (때로는 솔루션을 복사해서 붙여 넣기)은 이것에 대해 생각하지 않을 수도 있습니다.
Luca Borrione

6
-1 : 첫째, @ata는 이것의 대부분의 명령이 아무것도하지 않는 것이 옳습니다. 둘째, 단어 분리를 사용하여 배열을 형성하며, 그렇게 할 때 glob-expansion을 막기 위해 아무것도하지 않습니다 (따라서 배열 요소 중 하나에 glob 문자가 있으면 해당 요소는 일치하는 파일 이름으로 대체됩니다).
찰스 더피

1
사용 제안 $'...': IN=$'bla@some.com;john@home.com;bet <d@\ns* kl.com>'. 그런 다음 echo "${Array[2]}"줄 바꿈으로 문자열을 인쇄합니다. set -- "$IN"이 경우에도 필요합니다. 그렇습니다. glob 확장을 방지하려면 솔루션에을 포함해야합니다 set -f.
John_West

79

AWK 가 귀하의 문제를 해결하는 가장 효율적이고 효과적인 명령 이라고 생각 합니다. AWK는 기본적으로 거의 모든 Linux 배포판에 포함되어 있습니다.

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

줄게

bla@some.com john@home.com

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


3
또는 더 간단한 방법 : echo "bla@some.com; john@home.com"| awk 'BEGIN {RS = ";"} {print}'
Jaro

@Jaro 쉼표가있는 문자열이 있고 줄로 다시 포맷해야 할 때 완벽하게 작동했습니다. 감사.
Aquarelle

이 시나리오에서 작동했습니다-> "echo"$ SPLIT_0 "| awk -F 'inode =' '{print $ 1}'"! 문자 ( ";") 대신 아링 ( "inode =")을 사용하려고 할 때 문제가 발생했습니다. $ 1, $ 2, $ 3, $ 4는 배열에서 위치로 설정됩니다! 배열을 설정하는 방법이 있다면 더 좋습니다! 감사!
Eduardo Lucio

@EduardoLucio, 내가 생각하고있는 것은 먼저 구분 기호 를 예를 들어 inode=로 대체 한 다음 적용 시점 을 정의 하고 도움이 될 수 있기를 바랍니다. ;sed -i 's/inode\=/\;/g' your_file_to_process-F';'awk
Tong

66
echo "bla@some.com;john@home.com" | sed -e 's/;/\n/g'
bla@some.com
john@home.com

4
-1 문자열에 공백이 있으면 어떻게합니까? 예를 들어 IN="this is first line; this is second line" arrIN=( $( echo "$IN" | sed -e 's/;/\n/g' ) ),이 경우 2 개가 아닌 8 개 요소 (각 단어 공간에 대한 요소가 분리 된 요소)가 생성됩니다 (각 줄 세미콜론에 대한 요소가 분리됨)
Luca Borrione

3
@Luca sed 스크립트는 정확히 두 줄을 생성하지 않습니다. 여러 항목을 만드는 것은 bash 배열 (기본적으로 공백으로 분할 됨)에 넣을 때입니다.
lothar

바로 이것이 요점입니다. 편집에서 볼 수 있듯이 OP는 항목을 배열에 저장하여 반복합니다. 나는 당신의 (좋은) 대답이 arrIN=( $( echo "$IN" | sed -e 's/;/\n/g' ) )그것을 달성하기 위해 언급 IFS=$'\n'하고 미래에 여기에 착륙하고 공백을 포함하는 줄을 나눌 필요가있는 사람들 을 위해 IFS를 변경하라는 조언을 놓쳤다 고 생각합니다 . (그리고 나중에 다시 복원). :)
Luca Borrione

1
@Luca 좋은 지적. 그러나 그 답변을 작성했을 때 배열 할당은 초기 질문에 없었습니다.
lothar

65

이것은 또한 작동합니다 :

IN="bla@some.com;john@home.com"
echo ADD1=`echo $IN | cut -d \; -f 1`
echo ADD2=`echo $IN | cut -d \; -f 2`

이 솔루션이 항상 올바른 것은 아닙니다. "bla@some.com"만 전달하면 ADD1과 ADD2에 모두 할당됩니다.


1
-s를 사용하여 언급 된 문제를 피할 수 있습니다. superuser.com/questions/896800/… "-f, --fields = LIST이 필드 만 선택하고 -s 옵션이 아닌 경우 구분 문자가없는 행을 인쇄하십시오. 지정됨 "
fersarr

34

Darron의 답변 에 대한 다른 견해 는 내가하는 방법입니다.

IN="bla@some.com;john@home.com"
read ADDR1 ADDR2 <<<$(IFS=";"; echo $IN)

나는 그렇게 생각합니다! 위의 명령을 실행 한 다음 "echo $ ADDR1 ... $ ADDR2"를 실행하면 "bla@some.com ... john@home.com"출력이 표시됩니다.
nickjb

1
이것은 정말 잘 작동했습니다 ... mysqldump를 사용하기 위해 쉼표로 구분 된 DB, SERVER, PORT 데이터가 포함 된 문자열 배열을 처리하는 데 사용했습니다.
Nick

5
진단 : IFS=";"할당은 $(...; echo $IN)서브 쉘 에만 존재합니다 . 이것이 저를 포함한 일부 독자들이 처음에는 효과가 없다고 생각하는 이유입니다. ADDR1이 $ IN을 모두 잃어 버렸다고 가정했습니다. 그러나 nickjb는 정확합니다. 작동합니다. 이유는 echo $IN명령이 $ IFS의 현재 값을 사용하여 인수를 구문 분석 한 다음 $ IFS의 설정에 관계없이 공백 구분 기호를 사용하여 stdout으로 에코하기 때문입니다. 따라서 순 효과는 마치 호출 한 것과 같습니다 read ADDR1 ADDR2 <<< "bla@some.com john@home.com"(입력은 공백으로 분리되어;-분리되지 않음).
dubiousjim

1
이것은 공백과 개행에서 실패 하며 인용되지 않은 변수 확장으로 와일드 카드 *echo $IN확장합니다.
Isaac

나는이 솔루션을 정말 좋아한다. 왜 작동하는지에 대한 설명은 매우 유용하며 전체적으로 더 나은 답변이 될 것입니다.
Michael Gaskill 2016 년

32

Bash에서는 방탄 방법으로 변수에 줄 바꿈이 포함되어 있어도 작동합니다.

IFS=';' read -d '' -ra array < <(printf '%s;\0' "$in")

보기:

$ in=$'one;two three;*;there is\na newline\nin this field'
$ IFS=';' read -d '' -ra array < <(printf '%s;\0' "$in")
$ declare -p array
declare -a array='([0]="one" [1]="two three" [2]="*" [3]="there is
a newline
in this field")'

이 작업을 수행하는 비결 은 빈 구분 기호와 함께 (구분 기호) -d옵션 을 사용하는 read것이므로 read먹이는 모든 것을 읽습니다. 그리고 우리는 후행 줄 바꿈없이 read변수의 내용을 정확하게 공급 in합니다 printf. 우리는 또한에 구분을두고있어 주 printf문자열에 전달하도록 read후행 구분 기호를 가지고있다. 그것 없이는 read잠재적 인 후행 빈 필드를 다듬을 것입니다.

$ in='one;two;three;'    # there's an empty field
$ IFS=';' read -d '' -ra array < <(printf '%s;\0' "$in")
$ declare -p array
declare -a array='([0]="one" [1]="two" [2]="three" [3]="")'

후행 빈 필드는 유지됩니다.


Bash≥4.4 업데이트

Bash 4.4부터 내장 mapfile(일명 readarray)은 -d구분 기호를 지정하는 옵션을 지원합니다 . 따라서 또 다른 표준 방법은 다음과 같습니다.

mapfile -d ';' -t array < <(printf '%s;' "$in")

5
\n공백과 *동시에 올바르게 작동하는 목록에서 드문 솔루션으로 발견되었습니다 . 또한 루프가 없습니다. 실행 후 쉘에서 배열 변수에 액세스 할 수 있습니다 (높은 상향 응답과 달리). 참고 in=$'...', 그것은 큰 따옴표 작동하지 않습니다. 더 많은 투표가 필요하다고 생각합니다.
John_West

28

배열을 사용하지 않는 경우이 라이너는 어떻습니까?

IFS=';' read ADDR1 ADDR2 <<<$IN

사용을 고려 read -r ...,을 보장하기 위하여, 예를 들어, 입력의 두 문자 "\ t"는 당신의 변수에 동일한 두 개의 문자 (대신 단일 탭 문자)로 끝낸다.
dubiousjim

-1 여기서 작동하지 않습니다 (우분투 12.04). 추가 echo "ADDR1 $ADDR1"\n echo "ADDR2 $ADDR2"귀하의 미리보기가 출력에 ADDR1 bla@some.com john@home.com\nADDR2(\ n 개행 문자입니다)
루카 Borrione

이것은 4.3 IFS에 수정 된 문자열과 관련된 버그 때문일 수 bash있습니다. 인용 $IN하면 문제가 해결됩니다. (이론에서는 $IN단어가 확장 된 후 단어 분리 또는 글 로빙의 영향을받지 않으므로 따옴표는 필요하지 않습니다. 4.3에서도 그럼에도 불구하고보고 된 버그는 하나 이상 남아 있으며 수정 될 예정이므로 인용은 여전히 ​​유효합니다. idea.)
chepner

$ IN이 인용 되어도 $ in에 개행 문자가 있으면 중단됩니다. 그리고 후행 줄 바꿈을 추가합니다.
Isaac Isaac

이것과 다른 많은 솔루션의 문제점은 $ IN에 정확히 두 개의 요소가 있다고 가정하거나 ADDR2에서 두 번째 및 그 이후의 항목을 함께 버릴 수 있다는 가정입니다. 나는 이것이 요청에 부합한다는 것을 이해하지만 시한 폭탄입니다.
스티븐은 쉽게 즐겼다

22

IFS를 설정하지 않고

콜론이 하나만 있으면 그렇게 할 수 있습니다.

a="foo:bar"
b=${a%:*}
c=${a##*:}

당신은 얻을 것이다 :

b = foo
c = bar

20

깨끗한 3 라이너가 있습니다.

in="foo@bar;bizz@buzz;fizz@buzz;buzz@woof"
IFS=';' list=($in)
for item in "${list[@]}"; do echo $item; done

여기서 IFS구분 기호를 기준으로 단어를 구분하고 배열() 을 만드는 데 사용됩니다 . 그때[@] 각 항목을 별도의 단어로 반환하는 데 사용됩니다.

그 후에 코드가 있다면 $IFS, 예를 들어 복원해야합니다 unset IFS.


5
사용 $in하는 인용되지 않은 와일드 카드 확장 할 수 있습니다.
Isaac Isaac

10

다음 Bash / zsh 함수는 첫 번째 인수를 두 번째 인수가 제공 한 분리 문자로 분할합니다.

split() {
    local string="$1"
    local delimiter="$2"
    if [ -n "$string" ]; then
        local part
        while read -d "$delimiter" part; do
            echo $part
        done <<< "$string"
        echo $part
    fi
}

예를 들어

$ split 'a;b;c' ';'

수확량

a
b
c

예를 들어이 출력은 다른 명령으로 파이프 될 수 있습니다. 예:

$ split 'a;b;c' ';' | cat -n
1   a
2   b
3   c

주어진 다른 솔루션에 비해 다음과 같은 장점이 있습니다.

  • IFS무시되지 않음 : 로컬 변수조차도 동적 범위 지정으로 인해 IFS루프를 재정의 하면 새 값이 루프 내에서 수행 된 함수 호출로 누출됩니다.

  • 배열은 사용되지 않습니다.를 사용하여 문자열을 배열로 읽으 read려면 -aBash 및 -Azsh 의 플래그 가 필요합니다 .

원하는 경우 함수를 다음과 같이 스크립트에 넣을 수 있습니다.

#!/usr/bin/env bash

split() {
    # ...
}

split "$@"

1자를 초과하는 구분 기호로 작동하지 않는 것 같습니다 : split = $ (split "$ content" "file : //")
madprops

True-from help read:-d delim continue until the first character of DELIM is read, rather than newline
Halle Knast 2018 년

8

많은 상황에 awk를 적용 할 수 있습니다

echo "bla@some.com;john@home.com"|awk -F';' '{printf "%s\n%s\n", $1, $2}'

또한 이것을 사용할 수 있습니다

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

7

다음과 같이 간단하고 현명한 방법이 있습니다.

echo "add:sfff" | xargs -d: -i  echo {}

그러나 gnu xargs를 사용해야합니다. BSD xargs는 -d delim을 지원할 수 없습니다. 나처럼 애플 맥을 사용한다면 gnu xargs를 설치할 수 있습니다 :

brew install findutils

그때

echo "add:sfff" | gxargs -d: -i  echo {}

4

가장 간단한 방법입니다.

spo='one;two;three'
OIFS=$IFS
IFS=';'
spo_array=($spo)
IFS=$OIFS
echo ${spo_array[*]}

4

여기에 멋진 답변이 있습니다 (erator esp.). 그러나 다른 언어로 나눌 수있는 것과 비슷한 것이 있습니다.

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

이제 ${a[0]}, ${a[1]}등이 예상대로입니다. ${#a[*]}용어 수에 사용하십시오 . 또는 물론 반복하려면 :

for i in ${a[*]}; do echo $i; done

중요 사항:

이것은 걱정할 공간이 없어서 문제를 해결했지만 문제를 해결할 수없는 경우에 작동합니다. 이 경우 $IFS솔루션으로 이동하십시오 .


IN이메일 주소가 두 개 이상인 경우 작동하지 않습니다 . palindrom의 답변
olibre

${IN//;/ }두 개 이상의 값으로도 작동하도록 더 나은 사용 (더블 슬래시). 와일드 카드 ( *?[)는 확장 될 것입니다. 그리고 후행 빈 필드는 버려집니다.
Isaac Isaac

3
IN="bla@some.com;john@home.com"
IFS=';'
read -a IN_arr <<< "${IN}"
for entry in "${IN_arr[@]}"
do
    echo $entry
done

산출

bla@some.com
john@home.com

시스템 : 우분투 12.04.1


IFS는 read여기 의 특정 컨텍스트에서 설정되지 않으므로 나머지 코드가 있으면 화가 날 수 있습니다.
codeforester 2017 년

2

공간이 없다면, 왜 그렇지 않습니까?

IN="bla@some.com;john@home.com"
arr=(`echo $IN | tr ';' ' '`)

echo ${arr[0]}
echo ${arr[1]}

2

set내장을 사용하여 $@어레이 를로드하십시오 .

IN="bla@some.com;john@home.com"
IFS=';'; set $IN; IFS=$' \t\n'

그런 다음 파티를 시작하십시오.

echo $#
for a; do echo $a; done
ADDR1=$1 ADDR2=$2

set -- $IN대시로 시작하는 "$ IN"과 관련된 일부 문제를 피하는 데 더 좋습니다 . 여전히 인용 부호가없는 확장은 $IN와일드 카드 ( *?[)를 확장 합니다.
Isaac Isaac

2

bash 배열이 필요하지 않은 두 가지 번거로운 대안 :

사례 1 : 멋지고 단순하게 유지 : 레코드 구분자로 NewLine을 사용하십시오. 예 :

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

while read i; do
  # process "$i" ... eg.
    echo "[email:$i]"
done <<< "$IN"

참고 :이 첫 번째 경우 목록 조작을 돕기 위해 하위 프로세스가 분기되지 않습니다.

아이디어 : 내부적으로 NL을 광범위하게 사용 하고 최종 결과를 외부에서 생성 할 때 다른 RS로만 변환하는 것이 좋습니다.

사례 2 : ";"사용 레코드 구분 기호로 사용 ... 예 :

NL="
" IRS=";" ORS=";"

conv_IRS() {
  exec tr "$1" "$NL"
}

conv_ORS() {
  exec tr "$NL" "$1"
}

IN="bla@some.com;john@home.com"
IN="$(conv_IRS ";" <<< "$IN")"

while read i; do
  # process "$i" ... eg.
    echo -n "[email:$i]$ORS"
done <<< "$IN"

두 경우 모두 루프가 완료된 후에 루프 내에서 하위 목록을 구성 할 수 있습니다. 이것은 목록을 파일로 저장하는 대신 메모리에서 목록을 조작 할 때 유용합니다. {ps는 평온을 유지하고 B를 계속한다)}


2

이미 제공된 환상적인 답변 외에도 다음을 사용하여 고려할 수있는 데이터를 인쇄하는 것만 큼 awk:

awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "$IN"

이렇게하면 필드 구분 기호가로 설정되어 ;루프를 사용하여 필드를 for반복하여 그에 따라 인쇄 할 수 있습니다 .

테스트

$ IN="bla@some.com;john@home.com"
$ awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "$IN"
> [bla@some.com]
> [john@home.com]

다른 입력으로 :

$ awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "a;b;c   d;e_;f"
> [a]
> [b]
> [c   d]
> [e_]
> [f]

2

Android 셸에서 제안 된 방법 중 대부분은 작동하지 않습니다.

$ IFS=':' read -ra ADDR <<<"$PATH"                             
/system/bin/sh: can't create temporary file /sqlite_stmt_journals/mksh.EbNoR10629: No such file or directory

작동하는 것은 다음과 같습니다.

$ for i in ${PATH//:/ }; do echo $i; done
/sbin
/vendor/bin
/system/sbin
/system/bin
/system/xbin

여기서 //전역 교체를 의미합니다.


1
$ PATH의 일부에 공백이나 줄 바꿈이 있으면 실패합니다. 또한 와일드 카드 (별표 *, 물음표? 및 중괄호 […])를 확장합니다.
Isaac Isaac

2
IN='bla@some.com;john@home.com;Charlie Brown <cbrown@acme.com;!"#$%&/()[]{}*? are no problem;simple is beautiful :-)'
set -f
oldifs="$IFS"
IFS=';'; arrayIN=($IN)
IFS="$oldifs"
for i in "${arrayIN[@]}"; do
echo "$i"
done
set +f

산출:

bla@some.com
john@home.com
Charlie Brown <cbrown@acme.com
!"#$%&/()[]{}*? are no problem
simple is beautiful :-)

설명 : 괄호 ()를 사용한 단순 지정은 세미콜론으로 구분 된 목록을 올바른 IFS가있는 경우 배열로 변환합니다. 표준 FOR 루프는 평소와 같이 해당 배열의 개별 항목을 처리합니다. IN 변수에 제공된 목록은 "하드"따옴표로 묶어야합니다. 즉, 단일 틱이 있어야합니다.

Bash는 할당을 명령과 같은 방식으로 처리하지 않으므로 IFS를 저장하고 복원해야합니다. 다른 해결 방법은 할당을 함수 내부에 래핑하고 수정 된 IFS로 해당 함수를 호출하는 것입니다. 이 경우 별도의 IFS 저장 / 복원이 필요하지 않습니다. "Bize"를 지적 해 주셔서 감사합니다.


!"#$%&/()[]{}*? are no problem글쎄요 ... []*?확실 하지 않습니다 : 글로브 캐릭터입니다. `mkdir '! "# $ % &'; touch '!"# $ % & / () [] {}는 하 하하하-아무 문제 없습니다'명령을 실행하는 방법은 무엇입니까? 단순한 것은 아름답지만 깨지면 깨집니다.
gniourf_gniourf

@gniourf_gniourf 문자열은 변수에 저장됩니다. 원래 질문을 참조하십시오.
ajaaskel

1
@ajaaskel 당신은 내 의견을 완전히 이해하지 못했습니다. 스크래치 디렉토리로 이동하여 다음 명령을 실행하십시오 mkdir '!"#$%&'; touch '!"#$%&/()[]{} got you hahahaha - are no problem'.. 그들은 이상한 이름으로 디렉토리와 파일 만 만들 것입니다. 그런 다음 정확한 당신의 명령을 실행 IN하면 주었다 IN='bla@some.com;john@home.com;Charlie Brown <cbrown@acme.com;!"#$%&/()[]{}*? are no problem;simple is beautiful :-)'. 예상 한 결과를 얻지 못할 것입니다. 경로 이름 확장에 따라 메서드를 사용하여 문자열을 분할하기 때문입니다.
gniourf_gniourf

이 문자를 입증하는 *, ?, [...]경우에도, extglob설정, !(...), @(...), ?(...), +(...) 이다 이 방법에 문제가!
gniourf_gniourf

1
@gniourf_gniourf globbing에 대한 자세한 의견을 보내 주셔서 감사합니다. 나는 코드가 움켜 쥐도록 조정했다. 그러나 내 요점은 단지 간단한 할당이 분할 작업을 수행 할 수 있음을 보여주었습니다.
ajaaskel

1

얘들 아!

여기 내 대답이 있습니다!

DELIMITER_VAL='='

read -d '' F_ABOUT_DISTRO_R <<"EOF"
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.4 LTS"
NAME="Ubuntu"
VERSION="14.04.4 LTS, Trusty Tahr"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 14.04.4 LTS"
VERSION_ID="14.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
EOF

SPLIT_NOW=$(awk -F$DELIMITER_VAL '{for(i=1;i<=NF;i++){printf "%s\n", $i}}' <<<"${F_ABOUT_DISTRO_R}")
while read -r line; do
   SPLIT+=("$line")
done <<< "$SPLIT_NOW"
for i in "${SPLIT[@]}"; do
    echo "$i"
done

왜이 방법이 "최고"입니까?

두 가지 이유 때문에 :

  1. 분리 문자 를 벗어날 필요없습니다 .
  2. 빈 공간 에는 문제 가 없습니다 . 배열에서 값이 올바르게 분리됩니다!

[]'에스


참고로, /etc/os-release/etc/lsb-release공급, 구문 분석되지 않을 것을 의미한다. 따라서 귀하의 방법은 실제로 잘못되었습니다. 또한 구분 기호에서 문자열을 쪼개는
gniourf_gniourf

0

문자열을 ';'으로 구분하여 분할하는 하나의 라이너 배열로 :

IN="bla@some.com;john@home.com"
ADDRS=( $(IFS=";" echo "$IN") )
echo ${ADDRS[0]}
echo ${ADDRS[1]}

이것은 서브 쉘에 IFS 만 설정하므로 값을 저장하고 복원하는 것에 대해 걱정할 필요가 없습니다.


-1 여기서 작동하지 않습니다 (우분투 12.04). 그것은 $ IN 값을 가진 첫 번째 에코 만 인쇄하고 두 번째는 비어 있습니다. echo "0 :"$ {ADDRS [0]} \ n echo "1 :"$ {ADDRS [1]}을 출력하면 출력됩니다 0: bla@some.com;john@home.com\n 1:(\ n은 줄 바꿈)
Luca Borrione

1
이 아이디어 stackoverflow.com/a/6583589/1032370에 작동하는 대안에 nickjb의 답변을 참조하시기 바랍니다
루카 Borrione

1
-1, 1. IFS가 해당 서브 쉘에 설정되지 않았습니다 (내장 된 "echo"환경으로 전달되므로 아무 일도 일어나지 않습니다). 2. $IN인용되어 있으므로 IFS 분할이 적용되지 않습니다. 3. 프로세스 대체는 공백으로 분할되지만 원본 데이터가 손상 될 수 있습니다.
Score_Under

0

아마도 가장 우아한 해결책은 아니지만 *공백 과 함께 작동 합니다.

IN="bla@so me.com;*;john@home.com"
for i in `delims=${IN//[^;]}; seq 1 $((${#delims} + 1))`
do
   echo "> [`echo $IN | cut -d';' -f$i`]"
done

출력

> [bla@so me.com]
> [*]
> [john@home.com]

다른 예 (시작과 끝의 구분자) :

IN=";bla@so me.com;*;john@home.com;"
> []
> [bla@so me.com]
> [*]
> [john@home.com]
> []

기본적으로 예를 들어 ;만드는 것 이외의 모든 문자를 제거합니다 delims. ;;;. 그런 다음 로 계산하여 for에서 1로 반복 number-of-delimiters합니다 ${#delims}. 마지막 단계 $i는를 사용하여 안전한 부분을 얻는 것 cut입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.