dirname 및 basename 대 매개 변수 확장 성? 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

한 양식을 다른 양식보다 선호하는 객관적인 이유가 있습니까? 성능, 신뢰성, 휴대 성?

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

가장 중요한 경우는 슬래시가있는 경우입니다 (모든 슬래시 인 루트 디렉토리의 경우 포함). basenamedirname그들의 일을하기 전에 명령이 슬래쉬를 벗겨. 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.

참고로 :

이식성을 고려할 때 프로그램을 유지 관리하는 모든 시스템 을 고려해야 합니다 . POSIX가 전부는 아니므로 장단점이 있습니다. 장단점이 다를 수 있습니다.


답변

도 있습니다:

mkdir '
';    dir=$(basename ./'
');   echo "${#dir}"

0

해석과 파싱이 많고 두 프로세스가 이야기 할 때 필요한 나머지가 있기 때문에 그런 이상한 일이 발생합니다. 명령 대체는 후행 줄 바꿈을 제거합니다. 그리고 NUL (여기서는 분명히 관련이 없지만) . basenamedirname 또한 당신이 그들에게 얼마나 다른 이야기 않기 때문에 어떤 경우에 줄 바꿈을 후행 제거 할 것인가? 파일 이름에서 줄 바꿈 문자는 어쨌든 일종의 혐오이지만, 당신은 결코 알지 못합니다. 그리고 당신이 달리 할 수있을 때 결함이있는 길을가는 것은 합리적이지 않습니다.

여전히 … ${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.

… 어쩌면 의견이 산만해질 것입니다 ….


답변

당신의 프로세스에서 부스트를 얻을 수 basenamedirname(이러한 내장 명령하지 않은 이유를 이해하지 않는 -이없는 후보를하면, 나는 무엇인지 모른다) 그러나 핸들 것들 구현의 요구와 같은 :

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 구현 basenamedirname여러 인수 처리 또는 접미사 제거와 같은 특수 명령 줄 스위치를 추가했지만 셸에 추가하는 것은 매우 쉽습니다.)

bash기본 시스템 구현을 사용 하여 내장형 으로 만드는 것은 어렵지 않지만 위의 함수는 컴파일 할 필요가 없으며 약간의 향상도 제공합니다.