IFS (Internal Field Separator)가 여러 개의 연속 분리 문자 문자에 대한 단일 분리 자로 기능 할 수 있습니까?


10

공백이 아닌 공백 값으로 IFS를 사용하여 배열을 구문 분석하면 빈 요소가 작성됩니다.
심지어 사용하여 tr -s하나의 DELIM에 여러 delims을 축소하는 것만으로는 충분하지 않다.
예를 들어 문제를보다 명확하게 설명 할 수 있습니다.
IFS를 조정하여 "정상"결과를 얻을 수있는 방법이 있습니까? IFS의 동작을 변경하는 관련 설정이 있습니까? IFS.

var=" abc  def   ghi    "
echo "============== IFS=<default>"
arr=($var)
for x in ${!arr[*]} ; do
   echo "# arr[$x] \"${arr[x]}\""
done
#
sfi="$IFS" ; IFS=':'
set -f # Disable file name generation (globbing)
       # (This  data won't "glob", but unless globbing     
       #  is actually needed, turn if off, because   
       #  unusual/unexpected combinations of data can glob!
       #  and they can do it in the most obscure ways...  
       #  With IFS, "you're not in Kansas any more! :)  
var=":abc::def:::ghi::::"
echo "============== IFS=$IFS"
arr=($var)
for x in ${!arr[*]} ; do
   echo "# arr[$x] \"${arr[x]}\""
done
echo "============== IFS=$IFS and tr"
arr=($(echo -n "$var"|tr -s "$IFS"))
for x in ${!arr[*]} ; do
   echo "# arr[$x] \"${arr[x]}\""
done
set +f     # enable globbing 
IFS="$sfi" # re-instate original IFS val
echo "============== IFS=<default>"

출력은 다음과 같습니다


============== IFS=<default>
# arr[0] "abc"
# arr[1] "def"
# arr[2] "ghi"
============== IFS=:
# arr[0] ""
# arr[1] "abc"
# arr[2] ""
# arr[3] "def"
# arr[4] ""
# arr[5] ""
# arr[6] "ghi"
# arr[7] ""
# arr[8] ""
# arr[9] ""
============== IFS=: and tr
# arr[0] ""
# arr[1] "abc"
# arr[2] "def"
# arr[3] "ghi"
============== IFS=<default>

같은 질문에 대한 더 나은 대답이 있습니다 : stackoverflow.com/a/14789518/1765658
F. Hauri

답변:


3

여러 개의 (공백이 아닌) 연속 분리 문자 문자를 제거하기 위해 두 개의 (문자열 / 배열) 매개 변수 확장을 사용할 수 있습니다. 트릭은 IFS배열 매개 변수 확장을 위해 변수를 빈 문자열 로 설정하는 것 입니다.

이것은에 설명되어 있습니다 man bash에서 워드 분할 :

값이없는 매개 변수의 확장으로 인해 인용되지 않은 암시 적 널 인수가 제거됩니다.

(
set -f
str=':abc::def:::ghi::::'
IFS=':'
arr=(${str})
IFS=""
arr=(${arr[@]})

echo ${!arr[*]}

for ((i=0; i < ${#arr[@]}; i++)); do 
   echo "${i}: '${arr[${i}]}'"
done
)

좋은! bash 루프가 필요없고 유틸리티 앱인 BTW를 호출 할 필요가없는 간단하고 효과적인 방법입니다. "(non-space)"에 대해 언급했듯이 명확성을 위해 공간을 포함하여 구분 문자의 모든 조합에서 잘 작동한다고 지적합니다.
Peter.O

내 테스트에서 설정 IFS=' '(즉 공백)은 동일하게 작동합니다. 의 명시적인 null 인수 ( ""또는 '')보다 혼동이 적습니다 IFS.
Micha Wiedenmann

데이터에 공백이 포함되어 있으면 끔찍한 해결책입니다. 데이터가 'abc'대신 'bc'인 경우 IFS = ""는 'a'를 'bc'와는 별도의 요소로 분할합니다.
Dejay Clayton

5

에서 bash맨 :

인접한 IFS 공백 문자와 함께 IFS 공백이 아닌 IFS의 문자는 필드를 구분합니다. 일련의 IFS 공백 문자도 분리 문자로 처리됩니다.

이는 IFS 공백 (공백, 탭 및 줄 바꿈)이 다른 구분 기호처럼 취급되지 않음을 의미합니다 . 다른 구분 기호를 사용하여 정확히 동일한 동작을 원한다면 tr또는 의 도움으로 일부 구분 기호를 교환 할 수 있습니다 sed.

var=":abc::def:::ghi::::"
arr=($(echo -n $var | sed 's/ /%#%#%#%#%/g;s/:/ /g'))
for x in ${!arr[*]} ; do
   el=$(echo -n $arr | sed 's/%#%#%#%#%/ /g')
   echo "# arr[$x] \"$el\""
done

이것은 %#%#%#%#%필드 내부의 가능한 공간을 대체하는 마법의 가치이며, "고유 한"(또는 매우 무관하게) 것으로 예상됩니다. 필드에 공간이 없을 것이라고 확신하는 경우이 부분을 삭제하십시오.


@FussyS ... 고마워 (내 질문에 modificaton 참조) ... 당신은 나에게 의도 된 질문에 대한 답을 주었을지도 모른다. 그리고 그 대답은 (아마도)일지도 모른다. "내가 원하는 방식으로"... 나는 tr문제를 보여주기 위해 예제를 계획하고있다 ... 나는 시스템 호출을 피하고 싶다. 그래서 ${var##:}글렌의 답변자에 대한 나의 의견에서 언급 한 것 이외의 bash 옵션을 볼 것이다 .... 나는 잠시 기다릴 것이다. 아마 IFS를 동축시키는 방법이있을 것이다. 그렇지 않으면 당신의 대답의 첫 번째 부분은 다음이다. ...
Peter.O

이 처리는 IFS모든 Bourne 스타일 쉘에서 동일 하며 POSIX에 지정되어 있습니다.
Gilles 'SO- 악한 중지'

이 질문을 한 지 4 년이 지난 후에도 @nazad의 답변 (1 년 전에 게시 됨)은 IFS를 저글링하여 문자열을 임의의 수와 IFS문자로 구분 기호 문자열 로 만드는 가장 간단한 방법 인 것으로 나타났습니다 . 내 질문에 가장 잘 대답 jon_d했지만 @nazad의 답변은 IFS루프와 유틸리티 응용 프로그램없이 사용할 수있는 멋진 방법을 보여줍니다 .
Peter.O

2

bash IFS는 연속 분리 문자를 단일 분리 문자 (비 공백 구분 기호)로 처리하는 사내 방식을 제공하지 않기 때문에 모든 bash 버전을 구성했습니다 (예 : tr, awk, sed와 같은 외부 호출 사용) )

mult-char IFS를 처리 할 수 ​​있습니다.

다음은 이 Q / A 페이지에 나와있는 옵션 trawk옵션에 대한 유사한 테스트와 함께 실행 시간 결과입니다. 테스트는 (I / O없이) arrray를 구축하는 10000 개의 이터레이터를 기반으로합니다.

pure bash     3.174s (28 char IFS)
call (awk) 0m32.210s  (1 char IFS) 
call (tr)  0m32.178s  (1 char IFS) 

출력은 다음과 같습니다

# dlm_str  = :.~!@#$%^&()_+-=`}{][ ";></,
# original = :abc:.. def:.~!@#$%^&()_+-=`}{][ ";></,'single*quote?'..123:
# unified  = :abc::::def::::::::::::::::::::::::::::'single*quote?'::123:
# max-w 2^ = ::::::::::::::::
# shrunk.. = :abc:def:'single*quote?':123:
# arr[0] "abc"
# arr[1] "def"
# arr[2] "'single*quote?'"
# arr[3] "123"

여기 스크립트가 있습니다

#!/bin/bash

# Note: This script modifies the source string. 
#       so work with a copy, if you need the original. 
# also: Use the name varG (Global) it's required by 'shrink_repeat_chars'
#
# NOTE: * asterisk      in IFS causes a regex(?) issue,     but  *  is ok in data. 
# NOTE: ? Question-mark in IFS causes a regex(?) issue,     but  ?  is ok in data. 
# NOTE: 0..9 digits     in IFS causes empty/wacky elements, but they're ok in data.
# NOTE: ' single quote  in IFS; don't know yet,             but  '  is ok in data.
# 
function shrink_repeat_chars () # A 'tr -s' analog
{
  # Shrink repeating occurrences of char
  #
  # $1: A string of delimiters which when consecutively repeated and are       
  #     considered as a shrinkable group. A example is: "   " whitespace delimiter.
  #
  # $varG  A global var which contains the string to be "shrunk".
  #
# echo "# dlm_str  = $1" 
# echo "# original = $varG" 
  dlms="$1"        # arg delimiter string
  dlm1=${dlms:0:1} # 1st delimiter char  
  dlmw=$dlm1       # work delimiter  
  # More than one delimiter char
  # ============================
  # When a delimiter contains more than one char.. ie (different byte` values),    
  # make all delimiter-chars in string $varG the same as the 1st delimiter char.
  ix=1;xx=${#dlms}; 
  while ((ix<xx)) ; do # Where more than one delim char, make all the same in varG  
    varG="${varG//${dlms:$ix:1}/$dlm1}"
    ix=$((ix+1))
  done
# echo "# unified  = $varG" 
  #
  # Binary shrink
  # =============
  # Find the longest required "power of 2' group needed for a binary shrink
  while [[ "$varG" =~ .*$dlmw$dlmw.* ]] ; do dlmw=$dlmw$dlmw; done # double its length
# echo "# max-w 2^ = $dlmw"
  #
  # Shrik groups of delims to a single char
  while [[ ! "$dlmw" == "$dlm1" ]] ; do
    varG=${varG//${dlmw}$dlm1/$dlm1}
    dlmw=${dlmw:$((${#dlmw}/2))}
  done
  varG=${varG//${dlmw}$dlm1/$dlm1}
# echo "# shrunk.. = $varG"
}

# Main
  varG=':abc:.. def:.~!@#$%^&()_+-=`}{][ ";></,'\''single*quote?'\''..123:' 
  sfi="$IFS"; IFS=':.~!@#$%^&()_+-=`}{][ ";></,' # save original IFS and set new multi-char IFS
  set -f                                         # disable globbing
  shrink_repeat_chars "$IFS" # The source string name must be $varG
  arr=(${varG:1})    # Strip leading dlim;  A single trailing dlim is ok (strangely
  for ix in ${!arr[*]} ; do  # Dump the array
     echo "# arr[$ix] \"${arr[ix]}\""
  done
  set +f     # re-enable globbing   
  IFS="$sfi" # re-instate the original IFS
  #
exit

대단한 일, 흥미로운 +1!
F. Hauri

1

gawk로도 할 수 있지만 예쁘지 않습니다.

var=":abc::def:::ghi::::"
out=$( gawk -F ':+' '
  {
    # strip delimiters from the ends of the line
    sub("^"FS,"")
    sub(FS"$","")
    # then output in a bash-friendly format
    for (i=1;i<=NF;i++) printf("\"%s\" ", $i)
    print ""
  }
' <<< "$var" )
eval arr=($out)
for x in ${!arr[*]} ; do
  echo "# arr[$x] \"${arr[x]}\""
done

출력

# arr[0] "abc"
# arr[1] "def"
# arr[2] "ghi"

고마워 ... 내 주요 요청 (수정 된 질문)에서 명확하지 않은 것 같습니다 ... 그냥 내 $var것으로 변경하여 쉽게 할 수 있습니다 ${var##:}... 나는 실제로 IFS 자체를 조정하는 방법을 따랐습니다. 외부 호출 없이이 작업을 수행하려면 (Bash가 외부 호출보다 효율적 으로이 작업을 수행 할 수 있다고 생각합니다. 그래서 계속 추적 할 것입니다 ...) 방법이 작동합니다 (+1) .... 입력을 수정함에 따라 awk 또는 tr (시스템 호출을 피할 것)보다는 bash로 시도하는 것을 선호하지만 실제로 IFS 조정을 위해 놀고 있습니다 ...
Peter.O

@fred는 언급했듯이 IFS는 기본 공백 값에 대해 연속 된 여러 개의 델리 미터 만 표시합니다. 그렇지 않으면 연속 분리 문자로 인해 빈 필드가 생깁니다. 하나 또는 두 개의 외부 전화가 실제 방식으로 성능에 영향을 미치지 않을 것으로 예상합니다.
glenn jackman

@ glen .. (당신의 대답은 "예쁘지 않다"고 말했다. 나는 생각한다! :) 그러나, 나는 모든 배쉬 버전 (외부 호출에 대한)을 모으고 단지 arrray를 구축하는 10000 개의 itteritters ( no I / O) ... bash 1.276s... call (awk) 0m32.210s,, call (tr) 0m32.178s... ... 몇 번 그렇게하면 배쉬가 느리다고 생각할 수 있습니다! ...이 경우 awk가 더 쉬워 집니까? ... 스 니펫을 이미 가지고 있다면 :) ... 나중에 게시 할 것입니다. 지금 가야합니다.
Peter.O

그건 그렇고, gawk 스크립트를 다시 작성하십시오 ... 기본적으로 awk를 사용하지 않았으므로 세부적으로 살펴 보았습니다 ... 이유는 선택할 수 없지만 언급 할 것입니다. 인용 데이터를 제공하면 문제는 어쨌든 ..이 테스트 데이터를 따옴표 사이에 공백에서 따옴표 및 분할을 푼다 .. 따옴표의 홀수 번호를 충돌 ... 다음과 같습니다var="The \"X\" factor:::A single '\"' crashes:::\"One Two\""
Peter.O

-1

간단한 대답은 모든 구분자를 하나 (첫 번째)로 축소하는 것입니다.
루프가 필요합니다 ( log(N)시간 미만으로 실행 ).

 var=':a bc::d ef:#$%_+$$%      ^%&*(*&*^
 $#,.::ghi::*::'                           # a long test string.
 d=':@!#$%^&*()_+,.'                       # delimiter set
 f=${d:0:1}                                # first delimiter
 v=${var//["$d"]/"$f"};                    # convert all delimiters to
 :                                         # the first of the delimiter set.
 tmp=$v                                    # temporal variable (v).
 while
     tmp=${tmp//["$f"]["$f"]/"$f"};        # collapse each two delimiters to one
     [[ "$tmp" != "$v" ]];                 # If there was a change
 do
     v=$tmp;                               # actualize the value of the string.
 done

남은 일은 문자열을 하나의 구분 기호로 올바르게 나누고 인쇄하는 것입니다.

 readarray -td "$f" arr < <(printf '%s%s' "$v"'' "$f")
 printf '<%s>' "${arr[@]}" ; echo

set -fIFS를 변경할 필요가 없습니다.
공백, 개행 및 글로브 문자로 테스트되었습니다. 모든 일. 상당히 느리다 (쉘 루프가 예상됨에 따라).
그러나 bash에만 해당됩니다 ( -dreadarray 옵션 으로 인해 bash 4.4 이상 ).


쉘 버전은 배열을 사용할 수 없으며 사용 가능한 유일한 배열은 위치 매개 변수입니다.
사용 tr -s은 한 줄입니다 (스크립트에서 IFS는 변경되지 않음).

 set -f; IFS=$f command eval set -- '$(echo "$var" | tr -s "$d" "[$f*]" )""'

그리고 그것을 인쇄하십시오 :

 printf '<%s>' "$@" ; echo

여전히 느리지 만 그 이상은 아닙니다.

commandBourne에서는 명령 이 유효하지 않습니다.
zsh에서는 command외부 명령 만 호출하고 command사용하는 경우 평가에 실패합니다 .
ksh에서는로도 commandIFS의 값이 전역 범위에서 변경됩니다.
그리고 commandmksh 관련 쉘 (mksh, lksh, posh)에서 분할이 실패하게합니다. 명령 command을 제거 하면 코드가 더 많은 쉘에서 실행됩니다. 그러나 제거 command하면 IFS는 bash (posix 모드 없음) 및 zsh (기본 없음 (에뮬레이션 없음) 모드 제외) 대부분의 쉘 (eval은 특수 내장)에서 값을 유지합니다. 이 개념은 기본 zsh에서 또는없이 작동하도록 만들 수 없습니다 command.


여러 문자 IFS

예, IFS는 다중 문자 일 수 있지만 각 문자는 하나의 인수를 생성합니다.

 set -f; IFS="$d" command eval set -- '$(echo "$var" )""'
 printf '<%s>' "$@" ; echo

출력합니다 :

 <><a bc><><d ef><><><><><><><><><      ><><><><><><><><><
 ><><><><><><ghi><><><><><>

bash를 사용하면 commandsh / POSIX 에뮬레이션에없는 경우 단어를 생략 할 수 있습니다 . ksh93에서 명령이 실패합니다 (IFS는 변경된 값을 유지함). zsh에서이 명령 command은 zsh eval가 외부 명령 ( 찾을 수 없음) 으로 찾으려고 시도하지만 실패합니다.

하나의 분리 문자로 자동 축소되는 유일한 IFS 문자는 IFS 공백입니다.
IFS의 한 공간은 모든 연속 된 공간을 하나로 축소합니다. 하나의 탭은 모든 탭을 축소합니다. 하나의 공백 하나의 탭은 공백 및 / 또는 탭의 행을 하나의 구분자로 축소합니다. 줄 바꿈으로 아이디어를 반복하십시오.

여러 구분 기호를 축소하려면 약간의 저글링이 필요합니다.
입력에 ASCII 3 (0x03)이 사용되지 않는다고 가정하십시오 var.

 var=${var// /$'\3'}                       # protect spaces
 var=${var//["$d"]/ }                      # convert all delimiters to spaces
 set -f;                                   # avoid expanding globs.
 IFS=" " command eval set -- '""$var""'    # split on spaces.
 set -- "${@//$'\3'/ }"                    # convert spaces back.

ksh, zsh 및 bash (about command및 IFS) 에 대한 대부분의 주석이 여전히 여기에 적용됩니다.

값은 $'\0'텍스트 입력에서 가능성이 적지 만 bash 변수는 NUL ( 0x00)을 포함 할 수 없습니다 .

sh에는 동일한 문자열 연산을 수행하기위한 내부 명령이 없으므로 tr은 sh 스크립트에 대한 유일한 솔루션입니다.


예, OP가 요청한 쉘에 대해 Bash를 작성했습니다. 해당 쉘에서 IFS는 유지되지 않습니다. 그리고 예, zsh와 같이 이식성이 없습니다. @ StéphaneChazelas
아이작

bash와 zsh의 경우, sh로 호출 될 때 POSIX가 지정한대로 작동합니다
Stéphane Chazelas

@ StéphaneChazelas 각 쉘의 한계에 대한 추가 된 (많은) 노트.
Isaac

@ StéphaneChazelas 왜 downvote입니까?
Isaac

몰랐어요. BTW, command evalGilles의 IIRC에 관한 전담 Q & A가 있다고 생각합니다.
Stéphane Chazelas
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.