내가 읽고"쉘 매개변수 확장"에 대한 GNU 문서다음 구문이 제공됩니다.
${parameter//pattern/string}
파일 이름 확장과 마찬가지로 패턴을 확장하여 패턴을 생성합니다.범위확장되고 가장 긴 일치무늬 그에 대한 값은 다음으로 대체됩니다.끈...두 개의 슬래시로 구분되는 경우범위그리고무늬..., 모든 게임들무늬로 대체됩니다끈.
"모든 일치"라는 문구가 주어지면무늬로 대체됩니다끈"위에서 여러 인스턴스가 발생할 것으로 예상했습니다.무늬다음으로 교체끈 한꺼번에/g
, 정규식에서 전역 검색 및 바꾸기 명령에 플래그를 사용하는 방법 과 유사하게 단일 작업으로 수행됩니다 .
remove_from_path
다음과 같이 구현된 오픈 소스 코드(함수)가 있습니다 .
remove_from_path() {
local path_to_remove="$1"
local path_before
local result=":${PATH//\~/$HOME}:"
local counter=0
while [ "$path_before" != "$result" ]; do
counter+=1
echo "counter: $counter"
path_before="$result"
result="${result//:$path_to_remove:/:}"
done
result="${result%:}"
echo "${result#:}"
}
원래 코드에는 이 counter
변수가 포함되어 있지 않습니다. 루프가 실행될 반복 횟수를 확인하기 위해 이 변수를 추가했습니다 while
.
보시다시피 이 줄은 result="${result//:$path_to_remove:/:}"
GNU 문서에 언급된 것과 동일한 이중 슬래시 구문을 사용합니다. 이를 감안할 때 모든 인스턴스가 한 번에 삭제 while
되어야 하기 때문에 루프가 한 번만 실행되기를 원합니다 .path_to_remove
result
그러나 이는 사실이 아닌 것 같습니다. bash
shell( ) 에서 다음과 같이 version 3.2.57
업데이트했습니다 .$PATH
bash-3.2$ PATH="/foo/bar/baz:/foo/bar/baz:/foo/bar/baz:buzz"
그런 다음 위 함수를 셸에 복사/붙여넣고 실행했습니다. 나는 다음을 본다:
bash-3.2$ remove_from_path "/foo/bar/baz"
counter: 01
counter: 011
counter: 0111
buzz
루프가 3번 실행되는 것을 볼 수 있으므로 카운터 증가가 예상한 대로 작동하지 않는다는 사실을 무시하십시오 while
. ${parameter//pattern/string}
이중 슬래시 구문이 모든 일치 항목을 바꾸는 경우무늬그리고끈while
, 루프의 한 번의 반복에서 이것이 수행되지 않는 이유는 무엇입니까 ? 왜 3번의 반복이 필요한가요?
답변1
ksh93의 연산자 ${var//pattern/replacement}
(zsh, bash 및 mksh에서도 지원됨)는중복 없음패턴의 발생 횟수입니다.
${var//xxx/y}
중복되는 항목 중 4개를 대체하도록 변경하면 매우 xxxxxx
혼란스러울 것입니다.yy
yyyy
xxx
이는 $PATH
디렉토리 목록을 나타냅니다( ~
이 경우 ~
현재 작업 디렉토리의 하위 디렉토리 $HOME
로 변경하면 오류가 발생합니다).
많은 쉘(csh, tcsh, zsh, fish, yash)은 이를 배열 변수 중 하나에 매핑합니다.
$PATH
예를 들어, zsh에서 ( $path
와 같은 배열 에 매핑된 csh
) 디렉토리의 모든 항목을 제거하려면 다음을 수행하십시오.
path=( ${path:#$dir} )
(또는 path=( "${path[@]:#$dir}" )
빈 요소를 유지하지만 거기에 포함시키고 싶지는 않습니다 $PATH
.)
bash
이 작업을 수행하지 않지만 $PATH
Split+glob 연산자를 사용하여 배열로 변환할 수 있습니다.
set -o noglob
IFS=:
path=( $PATH'' )
ksh93이나 zsh와 같은 bash에서는 ${var//pattern/replacement}
구문을 사용하여 배열의 모든 요소에 적용할 수 있지만 "${array[@]//pattern/replacement}"
그렇게 할 수 없기 때문에 도움이 되지 않습니다.제거하다요소를 수정하면 됩니다.
따라서 bash
요소에 대해서만 반복할 수 있습니다.
remove_from_PATH() {
local - IFS=: dir to_remove result
set -o noglob
for dir in $PATH''; do
for to_remove do
if [[ $dir = "$to_remove" ]]; then
continue 2
fi
done
result+=( "$dir" )
done
PATH="${result[*]}"
}
( noglob과 같은 함수에 대한 로컬 local -
옵션을 변경하기 위해 Almquist 쉘에서 복사하려면 set -o
상대적으로 새로운 버전의 bash가 필요하며 사용 중인 것으로 보이는 고대 3.2 버전에서는 작동하지 않습니다.)
:
에 저장된 구분된 목록을 수정하여 요소를 제거하려면 $PATH
각 항목에 대해 다음이 필요합니다 $to_remove
.
$to_remove:
처음에 발견된 항목을 빈 문자열로 바꿉니다.:$to_remove:
중간에 있는 모든 항목(일부는:
s와 겹칠 수 있음)을 다음으로 교체합니다.:
:$to_remove
마지막에 빈 문자열 제거$PATH
만 포함되어 있으면$to_remove
선택의 여지가 없습니다. 비어 있으면$PATH
현재 디렉토리에서 명령을 검색하는 것이 마지막이므로 원하는 것이 아니기 때문입니다. 이는 버그로 더 잘 처리되어야 하며, 위에서 언급한 것처럼 실제 생활에서 일반적으로 발생하지 않는 병리적 상황으로 무시할 수 있습니다. 또는 찾기에서 아무 것도 찾지 못했는지/dev/null
확인할 수 있습니다.$PATH
그래서:
remove_from_PATH() {
local to_remove dir newpath="$PATH" prev_newpath
for to_remove do
while
prev_newpath=$newpath
newpath=${newpath#"$to_remove:"}
newpath=${newpath%":$to_remove"}
newpath=${newpath//":$to_remove:"/:}
[[ $newpath != "$prev_newpath" ]]
do
continue
done
done
if [[ -n $newpath ]]; then
PATH=$newpath
else
echo >&2 'Refusing to make $PATH empty'
return 1
fi
}
답변2
문제를 찾은 것 같은데, 틀렸다면 지금 알려주세요.
$result
변수 에는 다음 문자열이 있습니다.
:/foo/bar/baz:/foo/bar/baz:/foo/bar/baz:buzz:
적용하면 result="${result//:$path_to_remove:/:}"
모든 :/foo/bar/baz:
항목이 대체됩니다 :
. 그러나 패턴이 주어지면 두 번째 경로는 다음 :
과 같은 이유로 실제로 일치하지 않습니다.
:/foo/바/바즈:/foo/bar/baz:/foo/바/바즈:윙윙거리는 소리:
굵은 글씨의 경로는 패턴이 나타나는 곳입니다.
테스트를 위해 다음 방법을 시도해 볼 수 있습니다.
result=':/foo/bar/baz1:/foo/bar/baz2:/foo/bar/baz3:buzz:'
echo "${result//:'/foo/bar/baz'?:/:}"
#Output:
:/foo/bar/baz2:buzz:
위에서 볼 수 있듯이 두 번째 경로( /for/bar/baz2
)는 사용 중인 모드에 영향을 받지 않습니다.
따라서 매개변수 확장을 위해 다음을 수행할 수 있습니다.
echo "${r//'/foo/bar/baz':/}" # The firsy ':' in the pattern was removed
#and instead of replace the pattern with ':' I'm replacing with nothing.
따라서 remove_from_path
함수는 다음과 같아야 합니다.
remove_from_path() {
local path_to_remove="$1"
local path_before
local result=":${PATH//\~/$HOME}:"
local counter=0
while [ "$path_before" != "$result" ]; do
counter+=1
echo "counter: $counter"
path_before="$result"
result="${result//$path_to_remove:/}"
done
result="${result%:}"
echo "${result#:}"
}
그러나 함수의 논리에 따라 while 루프는 두 번 실행됩니다. 이는 매개변수 확장을 통해 다른 값을 설정하기 path_before
전에 변수가 설정 되기 때문입니다.result
답변3
선행 콜론이 너무 많습니다. 다음을 추가하지 말고 시도해 보세요.
result="/foo/bar/baz:/foo/bar/baz:/foo/bar/baz:buzz"
echo ${result//$path_to_remove:/:}
:::buzz
반복이 필요 없이 모든 항목을 한 번에 제거한다는 것을 알 수 있습니다. PATH
시스템 변수를 조작하면 세션을 사용할 수 없게 될 수 있다는 점 에 유의하세요 !