우리가 한 형식을 다른 형식보다 선호하는 객관적인 이유가 있습니까? 성능, 신뢰성, 휴대성?
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는 외부 바이너리를 사용합니다.)
답변1
불행히도 둘 다 고유한 특징이 있습니다.
POSIX에는 두 가지가 모두 필요하므로 둘 사이의 차이점은 이식성 문제가 아닙니다.
이러한 유틸리티를 사용하는 쉬운 방법은 다음과 같습니다.
base=$(basename -- "$filename")
dir=$(dirname -- "$filename")
평소와 같이 변수 대체 주위에 큰따옴표가 있고 --
파일 이름이 대시로 시작하는 경우 명령 뒤에 큰따옴표가 있습니다(그렇지 않으면 명령이 파일 이름을 옵션으로 해석합니다). 이것은 드물지만 악의적인 사용자에 의해 강제될 수 있는 극단적인 경우에는 여전히 실패합니다. 명령 대체는 후행 줄 바꿈을 제거합니다. 따라서 파일 이름이 이라고 하면 대신 로 설정 foo/bar
됩니다 . 해결 방법은 줄바꿈이 아닌 문자를 추가하고 명령 대체 후에 제거하는 것입니다.base
bar
bar
base=$(basename -- "$filename"; echo .); base=${base%.}
dir=$(dirname -- "$filename"; echo .); dir=${dir%.}
매개변수 대체를 사용하면 이상한 문자 확장과 관련된 극단적인 경우가 발생하지 않지만 슬래시 문자에는 많은 어려움이 있습니다. 전혀 엣지 케이스가 아닌 한 가지는 no 에 대해 디렉토리 부분 계산을 수행해야 한다는 것입니다 /
.
base="${filename##*/}"
case "$filename" in
*/*) dirname="${filename%/*}";;
*) dirname=".";;
esac
엣지 케이스는 뒤에 슬래시가 있는 케이스입니다(모두 슬래시인 루트 케이스 포함). basename
and 명령은 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
1 Solaris 10 및 이전 버전과 같은 사전 POSIX 셸을 사용하지 않는 한(아직 생산 중인 시스템에는 매개변수 확장 문자열 조작 기능이 없지만 POSIX 셸은 설치 시 항상 호출되지만 /bin/sh
그렇지 않습니다) .sh
/usr/xpg4/bin/sh
/bin/sh
²예 : foo
파일 업로드 서비스에 파일을 제출했지만 서비스가 이 문제로부터 보호하지 못한 후 해당 파일을 삭제하고 결과가 foo
삭제 됩니다.
답변2
둘 다 POSIX이므로 이식성은 "걱정할 필요가 없습니다". 더 빠르게 실행하려면 셸 교체를 고려해야 합니다.
하지만 - 휴대용이 무엇을 의미하는지에 따라 다릅니다. 일부 (불필요하게) 오래된 시스템에서는 이러한 기능을 구현하지 않았 /bin/sh
으며(Solaris 10 및 이전 버전), 반면에 얼마 전까지만 해도 개발자 dirname
들은 basename
.
참고로:
- basename - 경로명의 디렉토리가 아닌 부분을 반환합니다.(POSIX)
dirname - 경로 이름의 디렉터리 부분을 반환합니다.(POSIX)
dirname 유틸리티는 System III에 뿌리를 두고 있습니다. 이는 System V 릴리스 3의 설명에 지정된 요구 사항을 충족하기 위해 System V 릴리스를 통해 발전되었습니다. 4.3 BSD 및 이전 버전에는 디렉터리 이름이 포함되지 않습니다.
Solaris 10의 sh 매뉴얼 페이지(Oracle)
매뉴얼 페이지##
에는%/
.
이식성을 고려할 때 고려해야 할 사항모두나는 프로그램의 시스템을 유지 관리합니다. 모두 POSIX가 아니므로 장단점이 있습니다. 귀하의 장단점은 다를 수 있습니다.
답변3
게다가:
mkdir '
'; dir=$(basename ./'
'); echo "${#dir}"
0
이와 같은 이상한 일이 발생하는 이유는 두 프로세스가 통신할 때 발생해야 하는 많은 해석, 구문 분석 및 기타 작업이 있기 때문입니다. 명령 대체는 후행 줄 바꿈을 제거합니다. 그리고 널(분명히 여기서는 관련이 없지만). 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.
...댓글이 방해가 될 수도 있습니다...
답변4
프로세스 내에서 부스팅을 얻을 수 있지만 basename
( dirname
이것이 내장되어 있지 않은 이유를 이해할 수 없습니다. 후보가 아닌 경우 무엇인지 모르겠습니다) 구현에서는 다음과 같은 사항을 처리해야 합니다.
path dirname basename
"/usr/lib" "/usr" "lib"
"/usr/" "/" "usr"
"usr" "." "usr"
"/" "/" "/"
"." "." "."
".." "." ".."
^부터기본 이름(3)
그리고 다른 극단적인 경우.
나는 다음을 사용해왔다:
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
(기본 시스템 구현을 활용하여) 내장 함수를 만드는 것도 덜 어렵지만 위 함수는 컴파일이 필요하지 않으며 몇 가지 개선 사항을 제공합니다.