쉘 스크립트를 작성할 때 문자열을 분할하고 싶은 경우가 종종 있습니다. 다음은 매우 간단한 예입니다.
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/sbin
PATH
/*/*/*/../../../*/*/*/*/../../../*/*/*/*
빈 $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 셸에서는 공백, 탭 또는 줄 바꿈으로 분할됨) 이러한 명령 후에는 결국 비어 있는 상태로 설정됩니다(즉,분열 없음).
또 다른 잠재적인 문제는 메서드를 일반화하고 이를 다양한 함수에서 사용하는 경우 ...
위 섹션에서 동일한 작업을 수행하는 함수( $IFS
in 의 복사본 생성 $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