공백이 아닌 공백 값으로 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>
답변
여러 개의 (공백이 아닌) 연속 분리 문자 문자를 제거하기 위해 두 개의 (문자열 / 배열) 매개 변수 확장을 사용할 수 있습니다. 트릭은 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
맨 :
인접한 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
이것은 %#%#%#%#%
필드 내부의 가능한 공간을 대체하는 마법의 가치이며, “고유 한”(또는 매우 무관하게) 것으로 예상됩니다. 필드에 공간이 없을 것이라고 확신하는 경우이 부분을 삭제하십시오.
답변
bash IFS는 연속 분리 문자를 단일 분리 문자 (비 공백 구분 기호)로 처리하는 사내 방식을 제공하지 않기 때문에 모든 bash 버전을 구성했습니다 (예 : tr, awk, sed와 같은 외부 호출 사용) )
mult-char IFS를 처리 할 수 있습니다.
다음은 이 Q / A 페이지에 나와있는 옵션 tr
및 awk
옵션에 대한 유사한 테스트와 함께 실행 시간 결과입니다. 테스트는 (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
답변
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"
답변
간단한 대답은 모든 구분자를 하나 (첫 번째)로 축소하는 것입니다.
루프가 필요합니다 ( 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 -f
IFS를 변경할 필요가 없습니다.
공백, 개행 및 글로브 문자로 테스트되었습니다. 모든 일. 상당히 느리다 (쉘 루프가 예상됨에 따라).
그러나 bash에만 해당됩니다 ( -d
readarray 옵션 으로 인해 bash 4.4 이상 ).
쉬
쉘 버전은 배열을 사용할 수 없으며 사용 가능한 유일한 배열은 위치 매개 변수입니다.
사용 tr -s
은 한 줄입니다 (스크립트에서 IFS는 변경되지 않음).
set -f; IFS=$f command eval set -- '$(echo "$var" | tr -s "$d" "[$f*]" )""'
그리고 그것을 인쇄하십시오 :
printf '<%s>' "$@" ; echo
여전히 느리지 만 그 이상은 아닙니다.
command
Bourne에서는 명령 이 유효하지 않습니다.
zsh에서는 command
외부 명령 만 호출하고 command
사용하는 경우 평가에 실패합니다 .
ksh에서는로도 command
IFS의 값이 전역 범위에서 변경됩니다.
그리고 command
mksh 관련 쉘 (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를 사용하면 command
sh / 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 스크립트에 대한 유일한 솔루션입니다.