dirname और basename बनाम पैरामीटर विस्तार


20

क्या एक फॉर्म से दूसरे फॉर्म को पसंद करने का कोई वस्तुनिष्ठ कारण है? प्रदर्शन, विश्वसनीयता, पोर्टेबिलिटी?

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 बाहरी बायनेरिज़ का उपयोग करता है।)

जवाबों:


21

दोनों के पास अपने झगड़े हैं, दुर्भाग्य से।

दोनों 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 और पुराने के पूर्व-पॉसिक्स गोले का उपयोग नहीं कर रहे हैं /bin/sh(जो कि अभी भी उत्पादन में मशीनों पर पैरामीटर विस्तार स्ट्रिंग हेरफेर सुविधाओं का अभाव है - लेकिन shस्थापना में हमेशा एक POSIX शेल कहा जाता है, केवल यह है /usr/xpg4/bin/sh, नहीं /bin/sh)।
Submit उदाहरण के लिए: foo␤फ़ाइल अपलोड सेवा नामक एक फ़ाइल सबमिट करें जो इसके विरुद्ध सुरक्षा नहीं करती है, फिर इसे हटाएं और fooइसके बजाय हटाने का कारण बनें


वाह। तो ऐसा लगता है (किसी भी POSIX शेल में) सबसे मजबूत तरीका दूसरा है जिसका आप उल्लेख करते हैं? base=$(basename -- "$filename"; echo .); base=${base%.}; dir=$(dirname -- "$filename"; echo .); dir=${dir%.}? मैं ध्यान से पढ़ रहा था और मैंने आपको किसी भी कमियों का उल्लेख नहीं किया।
वाइल्डकार्ड

1
@Wildcard एक दोष यह है कि यह ऐसे व्यवहार करता foo/है foo, जैसे नहीं foo/., जो कि POSIX- अनुरूप उपयोगिताओं के अनुरूप नहीं है।
गिल्स एसओ- बुराई को रोकना '

मिल गया धन्यवाद। मुझे लगता है कि मैं अभी भी उस पद्धति को पसंद करता हूं, क्योंकि मुझे पता होगा कि क्या मैं निर्देशिकाओं से निपटने की कोशिश कर रहा हूं और /अगर मुझे ज़रूरत है तो मैं एक अनुगामी (या "वापस वापस लेना") कर सकता हूं।
वाइल्डकार्ड

"एक findपरिणाम के रूप में, जिसमें हमेशा एक निर्देशिका भाग होता है और जिसका कोई अनुगामी नहीं होता है /" पहले परिणाम के रूप में find ./आउटपुट नहीं होगा ./
तवियन बार्न्स

@ गिल्स नईलाइन चरित्र उदाहरण ने मेरे दिमाग को उड़ा दिया। उत्तर के लिए धन्यवाद
सैम थॉमस

10

दोनों POSIX में हैं, इसलिए पोर्टेबिलिटी को "बिना किसी चिंता के" होना चाहिए। शेल प्रतिस्थापन को तेजी से चलाने के लिए माना जाना चाहिए।

हालाँकि - यह इस बात पर निर्भर करता है कि आप पोर्टेबल से क्या मतलब है। कुछ (जरूरी नहीं) पुरानी प्रणालियों ने उन विशेषताओं को अपने /bin/sh(सोलारिस 10 और पुराने दिमाग में) लागू नहीं किया , जबकि दूसरी तरफ, कुछ समय पहले, डेवलपर्स को सावधान किया dirnameगया था जो उतना पोर्टेबल नहीं था basename

सन्दर्भ के लिए:

पोर्टेबिलिटी पर विचार करने में, मुझे उन सभी प्रणालियों को ध्यान में रखना होगा जहां मैं कार्यक्रम बनाए रखता हूं। सभी POSIX नहीं हैं, इसलिए ट्रेडऑफ़ हैं। आपके ट्रेडऑफ़ अलग हो सकते हैं।


7

वहाँ भी:

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

यह basenameसरल में पूरी तरह से POSIX अनुरूप है 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.

... शायद टिप्पणियाँ विचलित कर रही हैं ...।


1
वाह, फ़ाइल नाम में नई कहानियों को पीछे छोड़ने के बारे में अच्छी बात है। कीड़े का क्या कर सकते हैं। मुझे नहीं लगता कि मैं वास्तव में आपकी स्क्रिप्ट को समझता हूं, हालांकि। मैंने पहले कभी नहीं देखा [!/], क्या यह पसंद है [^/]? लेकिन आपकी टिप्पणी इसके साथ मेल नहीं खाती है ....
वाइल्डकार्ड

1
@Wildcard - अच्छा .. यह मेरी टिप्पणी नहीं है। वह मानक है । POSIX कल्पना basenameआपके शेल के साथ कैसे करें, इस पर निर्देशों का एक सेट है। लेकिन [!charclass]पोर्टेबल तरीका है कि ग्लब्स के साथ [^class]regex के लिए है - और गोले regex के लिए कल्पना नहीं कर रहे हैं। के बारे में टिप्पणी मिलान ... caseफिल्टर, इसलिए यदि मैं एक स्ट्रिंग जो स्लैश शामिल मेल खाते हैं / और एक !/तो अगर अगले मामले पैटर्न नीचे मैचों किसी भी अनुगामी /स्लैश सब पर वे केवल हो सकता है सभी स्लैश। और एक नीचे कि किसी भी अनुगामी /
mikeserv

2

आप इन-प्रोसेस से बढ़ावा पा सकते हैं 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"
}

(जीएनयू का मेरा नवीनतम कार्यान्वयन basenameऔर dirnameसामान के लिए कुछ विशेष फैंसी कमांड लाइन स्विच जोड़ता है जैसे कि कई तर्क या प्रत्यय स्ट्रिपिंग को संभालना, लेकिन यह शेल में जोड़ना आसान है।)

यह मुश्किल नहीं है कि इन्हें bashबिलिंस में बनाया जाए (अंतर्निहित सिस्टम कार्यान्वयन का उपयोग करके), लेकिन उपरोक्त फ़ंक्शन को संकलित करने की आवश्यकता नहीं है, और वे कुछ बढ़ावा भी प्रदान करते हैं।


किनारे के मामलों की सूची वास्तव में बहुत सहायक है। वे सभी बहुत अच्छे बिंदु हैं। सूची वास्तव में काफी पूर्ण लगती है; क्या वास्तव में कोई अन्य किनारे के मामले हैं?
वाइल्डकार्ड

मेरे पूर्व कार्यान्वयन ने x//सही तरह से चीजों को नहीं संभाला है, लेकिन मैंने उत्तर देने से पहले आपके लिए तय किया है। मुझे आशा है कि यह है।
PSkocik

इन उदाहरणों पर कार्य और निष्पादनयोग्य क्या करते हैं, इसकी तुलना करने के लिए आप एक स्क्रिप्ट चला सकते हैं। मुझे 100% मैच मिल रहा है।
PSkocik

1
आपका dirname फ़ंक्शन बार-बार स्लैश की घटनाओं को पट्टी करने के लिए प्रतीत नहीं होता है। उदाहरण के लिए: dirname a///b//c//d////eपैदावार a///b//c//d///
कोडफोर्स
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.