한 양식을 다른 양식보다 선호하는 객관적인 이유가 있습니까? 성능, 신뢰성, 휴대 성?
filename=/some/long/path/to/a_file
parentdir_v1="${filename%/*}"
parentdir_v2="$(dirname "$filename")"
basename_v1="${filename##*/}"
basename_v2="$(basename "$filename")"
echo "$parentdir_v1"
echo "$parentdir_v2"
echo "$basename_v1"
echo "$basename_v2"
생산 :
/some/long/path/to
/some/long/path/to
a_file
a_file
v1은 셸 매개 변수 확장을 사용하고 v2는 외부 바이너리를 사용합니다.
답변
불행히도 둘 다 단점이 있습니다.
둘 다 POSIX에 필요하므로 이들 간의 차이점은 이식성 문제 ¹가 아닙니다.
유틸리티를 사용하는 일반적인 방법은
base=$(basename -- "$filename")
dir=$(dirname -- "$filename")
--
파일 이름이 대시로 시작하는 경우 와 같이 변수 대체는 항상 따옴표로 묶고 명령 뒤에서 도 따옴표를 사용하십시오 (그렇지 않으면 명령이 파일 이름을 옵션으로 해석 함). 한 가지 경우에 여전히 실패하지만, 드물지만 악의적 인 사용자에 의해 강제 될 수 있습니다. 명령 대체는 후행 줄 바꿈을 제거합니다. 그래서 파일 이름이 호출되면 foo/bar
다음 base
에 설정됩니다 bar
대신 bar
. 해결 방법은 줄 바꿈이 아닌 문자를 추가하고 명령 대체 후 제거하는 것입니다.
base=$(basename -- "$filename"; echo .); base=${base%.}
dir=$(dirname -- "$filename"; echo .); dir=${dir%.}
매개 변수 대체를 사용하면 이상한 문자의 확장과 관련된 대소 문자가 발생하지 않지만 슬래시 문자에는 여러 가지 어려움이 있습니다. 전혀 중요하지 않은 한 가지는 디렉토리 부분을 계산하는 데없는 경우 다른 코드가 필요하다는 것입니다 /
.
base="${filename##*/}"
case "$filename" in
*/*) dirname="${filename%/*}";;
*) dirname=".";;
esac
가장 중요한 경우는 슬래시가있는 경우입니다 (모든 슬래시 인 루트 디렉토리의 경우 포함). basename
과 dirname
그들의 일을하기 전에 명령이 슬래쉬를 벗겨. POSIX 구문을 고수하면 후행 슬래시를 한 번에 제거 할 수는 없지만 두 단계로 수행 할 수 있습니다. 입력이 슬래시만으로 구성된 경우를 처리해야합니다.
case "$filename" in
*/*[!/]*)
trail=${filename##*[!/]}; filename=${filename%%"$trail"}
base=${filename##*/}
dir=${filename%/*};;
*[!/]*)
trail=${filename##*[!/]}
base=${filename%%"$trail"}
dir=".";;
*) base="/"; dir="/";;
esac
에지가 아닌 것을 알고 있다면 (예 : find
시작점 이외의 결과는 항상 디렉토리 부분을 포함하고 후행이 없음 /
) 매개 변수 확장 문자열 조작은 간단합니다. 모든 엣지 케이스에 대처해야하는 경우 유틸리티를 사용하기가 쉽지만 느립니다.
때때로, 당신은 foo/
처럼 foo/.
보다는 대우하고 싶을 수도 있습니다 foo
. 당신이 디렉토리 엔트리에 작용하는 경우 다음 foo/
에 해당 있어야하는데 foo/.
,하지 foo
; 이것은 foo
디렉토리에 foo
대한 심볼릭 링크 일 때 차이를 만듭니다. 심볼릭 링크를 foo/
의미하고 대상 디렉토리를 의미합니다. 이 경우, 슬래시가있는 경로의 기본 이름은 유리 .
하며 경로는 고유 한 이름 일 수 있습니다.
case "$filename" in
*/) base="."; dir="$filename";;
*/*) base="${filename##*/}"; dir="${filename%"$base"}";;
*) base="$filename"; dir=".";;
esac
빠르고 안정적인 방법은 zsh를 기록 수정 자 와 함께 사용하는 것입니다 (먼저 유틸리티와 같이 후행 슬래시를 제거합니다).
dir=$filename:h base=$filename:t
¹ 당신이 솔라리스 10 세의 같은 사전 POSIX 쉘을 사용하지 않는 /bin/sh
(매개 변수 확장 문자열 조작이 여전히 생산 시스템에 기능 부족 -하지만 항상 POSIX라는 쉘을 거기에 sh
설치 만 그건 /usr/xpg4/bin/sh
아니고 /bin/sh
).
² 예를 들어 foo
, 파일 업로드 서비스에 호출 된 파일을 보호하지 않는 파일을 제출 한 다음 삭제하고 foo
대신 삭제하십시오.
답변
둘 다 POSIX에 있으므로 이식성에 대해서는 걱정할 필요가 없습니다. 쉘 대체는 더 빨리 실행되는 것으로 가정해야합니다.
그러나 그것은 휴대용의 의미에 달려 있습니다. (필수적이지 않은) 일부 오래된 시스템은 /bin/sh
(Solaris 10 및 이전 버전을 염두에두고) 이러한 기능을 구현하지 않았지만 , 반면에 개발자는 이전 dirname
과 같이 이식성 이 떨어 졌다고 경고했습니다 basename
.
참고로 :
- basename-경로명의 비 디렉토리 부분을 리턴합니다 (POSIX)
-
dirname-경로명의 디렉토리 부분을 반환합니다 (POSIX)
dirname 유틸리티는 System III에서 시작되었습니다. System V 릴리스 3을 통해 System V 릴리스 3에서이 설명에 지정된 요구 사항과 일치하는 버전으로 발전했습니다. 4.3 BSD 및 이전 버전에는 dirname이 포함되지 않았습니다.
-
Solaris 10의 sh 매뉴얼 페이지 (Oracle)
의 이 매뉴얼 페이지에는##
또는이 언급되어 있지 않습니다%/
.
이식성을 고려할 때 프로그램을 유지 관리하는 모든 시스템 을 고려해야 합니다 . POSIX가 전부는 아니므로 장단점이 있습니다. 장단점이 다를 수 있습니다.
답변
도 있습니다:
mkdir '
'; dir=$(basename ./'
'); echo "${#dir}"
0
해석과 파싱이 많고 두 프로세스가 이야기 할 때 필요한 나머지가 있기 때문에 그런 이상한 일이 발생합니다. 명령 대체는 후행 줄 바꿈을 제거합니다. 그리고 NUL (여기서는 분명히 관련이 없지만) . basename
과dirname
또한 당신이 그들에게 얼마나 다른 이야기 않기 때문에 어떤 경우에 줄 바꿈을 후행 제거 할 것인가? 파일 이름에서 줄 바꿈 문자는 어쨌든 일종의 혐오이지만, 당신은 결코 알지 못합니다. 그리고 당신이 달리 할 수있을 때 결함이있는 길을가는 것은 합리적이지 않습니다.
여전히 … ${pathname##*/} != basename
그리고 마찬가지로 ${pathname%/*} != dirname
. 이러한 명령은 지정된 결과에 도달하기 위해 대부분 잘 정의 된 단계 시퀀스를 수행하도록 지정됩니다.
사양은 다음과 같지만 먼저 다음은 더 정확한 버전입니다.
basename()
case $1 in
(*[!/]*/) basename "${1%"${1##*[!/]}"}" ${2+"$2"} ;;
(*/[!/]*) basename "${1##*/}" ${2+"$2"} ;;
(${2:+?*}"$2") printf %s%b\\n "${1%"$2"}" "${1:+\n\c}." ;;
(*) printf %s%c\\n "${1##///*}" "${1#${1#///}}" ;;
esac
그것은 완벽하게 POSIX를 준수 basename
합니다.sh
. 어렵지 않습니다. 결과에 영향을 미치지 않고 아래에서 사용하는 몇 가지 지점을 병합했습니다.
사양은 다음과 같습니다.
basename()
case $1 in
("") # 1. If string is a null string, it is
# unspecified whether the resulting string
# is '.' or a null string. In either case,
# skip steps 2 through 6.
echo .
;; # I feel like I should flip a coin or something.
(//) # 2. If string is "//", it is implementation-
# defined whether steps 3 to 6 are skipped or
# or processed.
# Great. What should I do then?
echo //
;; # I guess it's *my* implementation after all.
(*[!/]*/) # 3. If string consists entirely of <slash>
# characters, string shall be set to a sin‐
# gle <slash> character. In this case, skip
# steps 4 to 6.
# 4. If there are any trailing <slash> characters
# in string, they shall be removed.
basename "${1%"${1##*[!/]}"}" ${2+"$2"}
;; # Fair enough, I guess.
(*/) echo /
;; # For step three.
(*/*) # 5. If there are any <slash> characters remaining
# in string, the prefix of string up to and
# including the last <slash> character in
# string shall be removed.
basename "${1##*/}" ${2+"$2"}
;; # == ${pathname##*/}
("$2"|\
"${1%"$2"}") # 6. If the suffix operand is present, is not
# identical to the characters remaining
# in string, and is identical to a suffix of
# the characters remaining in string, the
# the suffix suffix shall be removed from
# string. Otherwise, string is not modi‐
# fied by this step. It shall not be
# considered an error if suffix is not
# found in string.
printf %s\\n "$1"
;; # So far so good for parameter substitution.
(*) printf %s\\n "${1%"$2"}"
esac # I probably won't do dirname.
… 어쩌면 의견이 산만해질 것입니다 ….
답변
당신의 프로세스에서 부스트를 얻을 수 basename
및 dirname
(이러한 내장 명령하지 않은 이유를 이해하지 않는 -이없는 후보를하면, 나는 무엇인지 모른다) 그러나 핸들 것들 구현의 요구와 같은 :
path dirname basename
"/usr/lib" "/usr" "lib"
"/usr/" "/" "usr"
"usr" "." "usr"
"/" "/" "/"
"." "." "."
".." "." ".."
다른 에지 케이스.
나는 사용하고있다 :
basename(){
test -n "$1" || return 0
local x="$1"; while :; do case "$x" in */) x="${x%?}";; *) break;; esac; done
[ -n "$x" ] || { echo /; return; }
printf '%s\n' "${x##*/}";
}
dirname(){
test -n "$1" || return 0
local x="$1"; while :; do case "$x" in */) x="${x%?}";; *) break;; esac; done
[ -n "$x" ] || { echo /; return; }
set -- "$x"; x="${1%/*}"
case "$x" in "$1") x=.;; "") x=/;; esac
printf '%s\n' "$x"
}
(최신의 GNU 구현 basename
과 dirname
여러 인수 처리 또는 접미사 제거와 같은 특수 명령 줄 스위치를 추가했지만 셸에 추가하는 것은 매우 쉽습니다.)
bash
기본 시스템 구현을 사용 하여 내장형 으로 만드는 것은 어렵지 않지만 위의 함수는 컴파일 할 필요가 없으며 약간의 향상도 제공합니다.