쉘 프로그래밍에서 문자열을 분할하는 안전하고 이식 가능한 방법은 무엇입니까?

쉘 프로그래밍에서 문자열을 분할하는 안전하고 이식 가능한 방법은 무엇입니까?

쉘 스크립트를 작성할 때 문자열을 분할하고 싶은 경우가 종종 있습니다. 다음은 매우 간단한 예입니다.

for dir in $(echo $PATH | tr : " "); do
    [[ -x "$dir"/"$1" ]] && echo $dir
done

$1매우 간단하고 잘 작동하지만 $PATH의 디렉토리 이름에 공백이 포함되어 있으면 중단됩니다 .

루프 구분 기호가 발생할 때 문자열을 분할하는 권장 방법은 무엇입니까?

이상적으로 이 솔루션은 (상당히) 오래된 쉘(예: ksh88)에서 작동합니다.

답변1

확실한 해결책은 쉘 워드 분할을 사용하는 것이지만, 주의해야 할 몇 가지 문제가 있습니다.

IFS=:
set -o noglob
for dir in $PATH''; do
    dir=${dir:-.}
    [ -x "${dir%/}/$1" ] && printf "%s\n" "$dir"
done

set -o noglob변수가 인용되지 않으면 둘 다 필요하기 때문에분사그리고파일 이름 생성(와일드카드) 그것을 실행하려면 여기에 필요합니다분사$PATH(예를 들어, 가 포함된 가능성 없는 이벤트의 경우 및 ... 이 아닌 /usr/local/*bin*폴더에서 찾길 원하며 , 가 포함된 경우 컴퓨터가 다운되는 것을 원하지 않습니다.)/usr/local/*bin*/usr/local/bin/usr/local/sbinPATH/*/*/*/../../../*/*/*/*/../../../*/*/*/*

$PATH구성요소는 현재 디렉토리( .)를 나타내며, 이 경우에는 올바르지 않습니다 /. $dir/$1이 경우 해결 방법은 를 사용할 때 ( $dir${dir:+/}$1를 쓰거나 변경하는 것 입니다 .$dir.printf '%s\n' "$dir"

//foo반드시 와 동일할 필요는 없으므로 에 있으면 /foo에 있을 필요는 없으며 이는 입니다 . 따라서 후행 슬래시를 제거하십시오./$PATH$dir/$1//$1${dir%/}

그런 다음 몇 가지 다른 질문이 있습니다.

의 경우 필드입니다 $PATH.":"분할기그리고 의 경우 $IFS필드입니다.터미네이터(예, 알아요 S.에스쪼개는 도구, ksh 동작을 표준화하는 ksh 및 POSIX에 기인함).

따라서 $PATH이것이 /usr/bin:/bin:(나쁜 습관이지만 여전히 일반적인 경우) , and (즉, 현재 디렉토리)를 의미하고 쉘 단어 분할( 을 제외한 모든 POSIX 쉘 "/usr/bin") 은 and 로만 분할됩니다 ."/bin"""zsh/usr/bin/bin

설정 되었지만 $PATH비어 있으면 다음을 의미합니다."현재 디렉터리에서만 찾기". 그리고 쉘(구분자로 처리하는 쉘 포함 $IFS)은 이를 빈 목록으로 확장합니다.

''위 의 내용을 추가하면 $PATH두 가지 문제가 모두 해결됩니다.

마지막으로 중요한 것은. 설정되지 않은 경우 $PATH특별한 의미가 있습니다.시스템 기본 검색 목록 보기, 불행하게도 누구에게(어떤 명령을) 물어보느냐에 따라 의미가 달라집니다.

$ env -u PATH bash -c 'type usbipd'
usbipd is /usr/local/sbin/usbipd
$ env -u PATH ksh -c 'type usbipd'
ksh: whence: usbipd: not found

기본적으로 스크립트에서는 자신에게 중요한 맥락에서 기본 검색 경로가 무엇인지 추측해야 합니다.

설정되지 않거나 비어 있으면 POSIX는 지정되지 않은 동작을 유지하므로 $PATH도움이 되지 않습니다. 이는 또한 위에서 말한 내용이 일부 과거, 현재 또는 미래의 POSIX/Unix 시스템에 적용되지 않을 수도 있음을 의미합니다.

간단히 말해서 $PATH명령이 실행된 위치를 파악하기 위해 구문 분석하는 것은 까다로운 작업입니다.

다음과 같은 표준 명령이 있습니다 command.

ls_path=$(command -v ls)

하지만 사람들은 이렇게 물을 수도 있습니다. 왜 알고 싶나요?

이제 IFS를 기본값으로 복원합니다.

oldIFS=$IFS
IFS=:
...
IFS=$oldIFS

대부분의 경우 실제로 작동하지만 POSIX와의 작동이 보장되지는 않습니다.

그 이유는 $IFS이전에 설정되지 않은 경우 이는 다음을 의미하기 때문입니다.기본 분할 동작(즉, POSIX 셸에서는 공백, 탭 또는 줄 바꿈으로 분할됨) 이러한 명령 후에는 결국 비어 있는 상태로 설정됩니다(즉,분열 없음).

또 다른 잠재적인 문제는 메서드를 일반화하고 이를 다양한 함수에서 사용하는 경우 ...위 섹션에서 동일한 작업을 수행하는 함수( $IFSin 의 복사본 생성 $oldIFS)를 호출하는 경우 원본 $oldIFS및 오류를 복원하십시오 $IFS.

대신 가능한 경우 하위 쉘을 사용할 수 있습니다.

(
  IFS=:
  ...
)
# only the subshell's IFS was affected, the parent still has its own IFS

내 접근 방식은 $IFS를 설정하고 켜 set -o noglob거나 끄는 것입니다.매번단어 분할(드문 경우)이 필요하며 이전 값을 복원하는 데 신경 쓰지 않습니다. 물론 스크립트가 이 관행을 따르지 않고 기본 단어 분리 동작을 채택하는 다른 사람의 코드를 호출하는 경우에는 작동하지 않습니다.

답변2

필요에 따라 설정하고 IFS쉘이 단어 분할을 수행하도록 하십시오.

IFS=':'
for dir in $PATH; do
    [ -x "$dir"/"$1" ] && echo $dir
done

bash이는 , dash및 에서 작동 ksh하지만 최신 버전에서만 테스트되었습니다.

답변3

고정된 수의 필드를 변수로 읽어야 하는 경우 다음 방법을 사용할 수 있습니다.

input="age:30"

IFS=':' read -r first_field second_field <<< "$input"

echo "$first_field"
echo "$second_field"

나는 그것을 찾았다그렉의 위키.

백슬래시를 특별하게 취급해서는 안 된다는 점을 알려줍니다 -r.read

관련 정보