bash - खुद को पूरा रास्ता पाने के लिए एक बैश स्क्रिप्ट के लिए विश्वसनीय तरीका?



path (16)

इस प्रश्न का उत्तर यहां दिया गया है:

मेरे पास एक बैश स्क्रिप्ट है जिसे इसके पूर्ण पथ को जानने की आवश्यकता है। मैं सापेक्ष या फंकी दिखने वाले पथों के समाप्त होने के बिना ऐसा करने का एक व्यापक रूप से संगत तरीका खोजने की कोशिश कर रहा हूं। मुझे केवल बैश का समर्थन करने की जरूरत है, न कि sh, csh, आदि

जो मैंने अभी तक पाया है:

  1. स्क्रिप्ट का मार्ग dirname $0 माध्यम से स्क्रिप्ट का मार्ग प्राप्त करने वाले पते को " बैश स्क्रिप्ट की स्रोत निर्देशिका प्राप्त करने" के स्वीकार्य उत्तर को ठीक किया गया है, लेकिन यह एक सापेक्ष पथ (जैसे . ) वापस कर सकता है, जो एक समस्या है यदि आप चाहते हैं स्क्रिप्ट में निर्देशिकाओं को बदलने के लिए और पथ अभी भी स्क्रिप्ट की निर्देशिका को इंगित करता है। फिर भी, पहेली नाम का हिस्सा होगा।

  2. " ओएसएक्स के साथ बैश स्क्रिप्ट पूर्ण पथ " के लिए स्वीकार्य उत्तर (ओएस एक्स विशिष्ट, लेकिन उत्तर बिना किसी काम करता है) एक ऐसा फ़ंक्शन देता है जो यह देखने के लिए जांच करेगा कि $0 रिश्तेदार दिखता है और यदि ऐसा है तो उसे $PWD प्री-पेंड करेगा। लेकिन परिणाम में अभी भी सापेक्ष बिट्स हो सकती हैं (हालांकि कुल मिलाकर यह पूर्ण है) - उदाहरण के लिए, यदि स्क्रिप्ट निर्देशिका /usr/bin और आप /usr और आप bin/../bin/t टाइप करते हैं इसे चलाने के लिए (हाँ, यह संकलित है), आप स्क्रिप्ट के निर्देशिका पथ के रूप में /usr/bin/../bin साथ समाप्त होते हैं। कौन सा काम करता है , लेकिन ...

  3. इस पृष्ठ पर readlink समाधान, जो इस तरह दिखता है:

    # Absolute path to this script. /home/user/bin/foo.sh
    SCRIPT=$(readlink -f $0)
    # Absolute path this script is in. /home/user/bin
    SCRIPTPATH=`dirname $SCRIPT`
    

    लेकिन readlink नहीं है और स्पष्ट रूप से समाधान जीएनयू के readlink पर निर्भर करता है जहां बीएसडी कुछ कारणों से काम नहीं करेगा (मुझे बीएसडी जैसी प्रणाली की जांच करने की सुविधा नहीं है)।

तो, इसे करने के विभिन्न तरीके, लेकिन वे सभी अपनी चेतावनी है।

एक बेहतर तरीका क्या होगा? जहां "बेहतर" का अर्थ है:

  • मुझे पूर्ण पथ देता है।
  • एक गड़बड़ तरीके से बुलाए जाने पर भी फंकी बिट्स लेता है (ऊपर # 2 पर टिप्पणी देखें)। (उदाहरण के लिए, कम से कम मामूली रूप से पथ को कैनोनिकल करता है।)
  • बैश-आईम्स या चीजों पर निर्भर करता है जो * निक्स सिस्टम (जीएनयू / लिनक्स, बीएसडी और बीएसडी जैसी प्रणालियों जैसे ओएस एक्स इत्यादि) के सबसे लोकप्रिय स्वादों पर लगभग निश्चित हैं।
  • यदि संभव हो तो बाहरी कार्यक्रमों को कॉल करने से बचें (उदाहरण के लिए, बैश बिल्ट-इन्स पसंद करते हैं)।
  • ( अपडेट किया गया , हेड अप के लिए धन्यवाद, wich ) सिम्लिंक को हल करने की ज़रूरत नहीं है (असल में, मैं इसे पसंद करना चाहूंगा, लेकिन यह एक आवश्यकता नहीं है)।

खोल स्क्रिप्ट का पूर्ण पथ प्राप्त करें

रीडलिंक में -f विकल्प का उपयोग नहीं करता है, इसलिए बीएसडी / मैक-ओएसएक्स में काम करना चाहिए

समर्थन

  • स्रोत ./script (जब डॉट ऑपरेटर द्वारा बुलाया जाता है)
  • पूर्ण पथ / पथ / से / स्क्रिप्ट
  • सापेक्ष पथ जैसे ./script
  • /path/dir1/../dir2/dir3/../script
  • जब सिम्लिंक से बुलाया जाता है
  • जब foo->dir1/dir2/bar bar->./../doe doe->script उदाहरण के लिए) foo->dir1/dir2/bar bar->./../doe doe->script
  • जब कॉलर स्क्रिप्ट नाम बदलता है

मैं कोने के मामलों की तलाश में हूं जहां यह कोड काम नहीं करता है । कृपया मुझे बताओ।

कोड

pushd . > /dev/null
SCRIPT_PATH="${BASH_SOURCE[0]}";
while([ -h "${SCRIPT_PATH}" ]); do
    cd "`dirname "${SCRIPT_PATH}"`"
    SCRIPT_PATH="$(readlink "`basename "${SCRIPT_PATH}"`")";
done
cd "`dirname "${SCRIPT_PATH}"`" > /dev/null
SCRIPT_PATH="`pwd`";
popd  > /dev/null
echo "srcipt=[${SCRIPT_PATH}]"
echo "pwd   =[`pwd`]"

ज्ञात जारी

स्क्रिप्ट कहीं डिस्क पर होनी चाहिए , इसे नेटवर्क पर रहने दें। यदि आप इस स्क्रिप्ट को पीआईपीई से चलाने का प्रयास करते हैं तो यह काम नहीं करेगा

wget -o /dev/null -O - http://host.domain/dir/script.sh |bash

तकनीकी रूप से बोलते हुए, यह अनिर्धारित है।
व्यावहारिक रूप से बोलते हुए, इसका पता लगाने के लिए कोई रास्ता नहीं है। (सह-प्रक्रिया माता-पिता के env तक नहीं पहुंच सकती है)


sh अनुरूप तरीका:

SCRIPT_HOME=`dirname $0 | while read a; do cd $a && pwd && break; done`

आप निम्न चर को परिभाषित करने का प्रयास कर सकते हैं:

CWD="$(cd -P -- "$(dirname -- "$0")" && pwd -P)"

या आप निम्न फ़ंक्शन को बैश में आज़मा सकते हैं:

realpath () {
  [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"
}

यह कार्य 1 तर्क लेता है। यदि तर्क पहले से ही पूर्ण पथ है, तो इसे प्रिंट करें, अन्यथा $PWD चर + फ़ाइल नाम तर्क (बिना ./ उपसर्ग) मुद्रित करें।

सम्बंधित:


इस मुद्दे को दोबारा ध्यान में रखते हुए: एक बहुत लोकप्रिय समाधान है जिसे इस धागे के संदर्भ में संदर्भित किया गया है जिसकी उत्पत्ति here :

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

मैं इस समाधान से दूर रह गया हूं क्योंकि डायरनाम के उपयोग की वजह से - यह क्रॉस-प्लेटफॉर्म कठिनाइयों को प्रस्तुत कर सकता है, खासकर यदि सुरक्षा कारणों से किसी स्क्रिप्ट को लॉक करने की आवश्यकता है। लेकिन एक शुद्ध बैश विकल्प के रूप में, उपयोग करने के बारे में कैसे:

DIR="$( cd "$( echo "${BASH_SOURCE[0]%/*}" )" && pwd )"

क्या यह एक विकल्प होगा?


इसे इस्तेमाल करे:

cd $(dirname $([ -L $0 ] && readlink -f $0 || echo $0))

उपयोग करने के बारे में क्या:

SCRIPT_PATH=$(dirname `which $0`)

which निष्पादन योग्य के पूर्ण पथ को stdout करने के लिए प्रिंट करता है which निष्पादित किया गया था जब शेल प्रॉम्प्ट पर पारित तर्क दर्ज किया गया था (जो $ 0 है)

dirname फ़ाइल नाम से गैर निर्देशिका प्रत्यय स्ट्रिप्स

इसलिए आप जिस चीज के साथ समाप्त होते हैं वह स्क्रिप्ट का पूरा मार्ग है, भले ही पथ निर्दिष्ट किया गया हो या नहीं।


ऐसा करने का एक और तरीका:

shopt -s extglob

selfpath=$0
selfdir=${selfpath%%+([!/])}

while [[ -L "$selfpath" ]];do
  selfpath=$(readlink "$selfpath")
  if [[ ! "$selfpath" =~ ^/ ]];then
    selfpath=${selfdir}${selfpath}
  fi
  selfdir=${selfpath%%+([!/])}
done

echo $selfpath $selfdir

चूंकि मेरे लिनक्स सिस्टम पर प्रति डिफ़ॉल्ट रूप से रीयलपैथ स्थापित नहीं है, तो मेरे लिए निम्न कार्य करता है:

SCRIPT="$(readlink --canonicalize-existing "$0")"
SCRIPTPATH="$(dirname "$SCRIPT")"

$SCRIPT लिए वास्तविक फ़ाइल पथ और स्क्रिप्ट युक्त निर्देशिका का वास्तविक पथ $SCRIPTPATH होगा।

इसका उपयोग करने से पहले इस उत्तर की टिप्पणियां पढ़ें।


बस इसके नरक के लिए मैंने एक स्क्रिप्ट पर हैकिंग का थोड़ा सा काम किया है जो चीजों को पूरी तरह से पाठ में पूरी तरह से पाठ करता है। मुझे आशा है कि मैंने सभी किनारे के मामलों को पकड़ा है। ध्यान दें कि मैंने दूसरे उत्तर में उल्लेख किया गया ${var//pat/repl} काम नहीं करता है क्योंकि आप इसे केवल सबसे कम संभव मिलान को प्रतिस्थापित नहीं कर सकते हैं, जो /foo/../ को बदलने की समस्या है उदाहरण के लिए /*/../ इससे पहले सब कुछ ले जाएगा, सिर्फ एक ही प्रविष्टि नहीं। और चूंकि ये पैटर्न वास्तव में regexes नहीं हैं, मैं नहीं देखता कि यह काम करने के लिए कैसे किया जा सकता है। तो यहां पर अच्छी तरह से घुलनशील समाधान है जिसका मैं आया हूं, आनंद लें। ;)

वैसे, अगर आपको कोई अनचाहे किनारे के मामले मिलते हैं तो मुझे बताएं।

#!/bin/bash

canonicalize_path() {
  local path="$1"
  OIFS="$IFS"
  IFS=$'/'
  read -a parts < <(echo "$path")
  IFS="$OIFS"

  local i=${#parts[@]}
  local j=0
  local back=0
  local -a rev_canon
  while (($i > 0)); do
    ((i--))
    case "${parts[$i]}" in
      ""|.) ;;
      ..) ((back++));;
      *) if (($back > 0)); then
           ((back--))
         else
           rev_canon[j]="${parts[$i]}"
           ((j++))
         fi;;
    esac
  done
  while (($j > 0)); do
    ((j--))
    echo -n "/${rev_canon[$j]}"
  done
  echo
}

canonicalize_path "/.././..////../foo/./bar//foo/bar/.././bar/../foo/bar/./../..//../foo///bar/"

बस: BASEDIR=$(readlink -f $0 | xargs dirname)

कोई फैंसी ऑपरेटर नहीं

HIH।


मुझे बस इस मुद्दे पर फिर से जाना पड़ा और https://.com/a/246128/1034080 मिला। यह एक ऐसे समाधान पर विस्तारित करता है जिसे मैंने अतीत में भी उपयोग किया है

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

लिंक किए गए उत्तर पर और अधिक भिन्नताएं हैं, उदाहरण के लिए जहां स्क्रिप्ट स्वयं एक सिम्लिंक है।


मुझे हैरान है कि realpath कमांड का उल्लेख नहीं किया गया है। मेरी समझ यह है कि यह व्यापक रूप से पोर्टेबल / पोर्ट किया गया है।

आपका प्रारंभिक समाधान बन जाता है:

SCRIPT=`realpath $0`
SCRIPTPATH=`dirname $SCRIPT`

और अपनी वरीयता के अनुसार अनन्य लिंक अनसुलझे छोड़ने के लिए:

SCRIPT=`realpath -s $0`
SCRIPTPATH=`dirname $SCRIPT`

यदि हम बैश का उपयोग करते हैं तो मेरा मानना ​​है कि यह सबसे सुविधाजनक तरीका है क्योंकि इसे किसी बाहरी आदेश पर कॉल की आवश्यकता नहीं होती है:

THIS_PATH="${BASH_SOURCE[0]}";
THIS_DIR=$(dirname $THIS_PATH)

यहां मैंने जो कुछ किया है (संपादित करें: साथ ही sfstewman , levigroker , केली स्ट्रैंड और रॉब केनेडी द्वारा प्रदान किए गए कुछ बदलाव), जो कि मेरे "बेहतर" मानदंडों में अधिकतर फिट बैठते हैं:

SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"

वह SCRIPTPATH लाइन विशेष रूप से चौराहे लगती है, लेकिन रिक्त स्थान और SCRIPTPATH=`pwd` को व्यवस्थित करने के लिए SCRIPTPATH=`pwd` बजाय इसकी आवश्यकता है।

ध्यान दें कि गूढ़ परिस्थितियां, जैसे किसी स्क्रिप्ट को निष्पादित करना जो किसी फ़ाइल से किसी फ़ाइल से नहीं आ रहा है (जो पूरी तरह से संभव है), वहां पर पूरा नहीं किया गया है (या मैंने देखा है कि किसी भी अन्य उत्तर में )।


स्वीकार्य समाधान में असुविधाजनक (मेरे लिए) "स्रोत-सक्षम" नहीं होना चाहिए:
यदि आप इसे " source ../../yourScript " से कॉल करते हैं, तो $0 " bash " होगा!

निम्न फ़ंक्शन (bash> = 3.0 के लिए) मुझे सही पथ देता है, हालांकि स्क्रिप्ट को सीधे या source माध्यम से, पूर्ण या सापेक्ष पथ के साथ कहा जा सकता है):
("दाएं पथ" से, मेरा मतलब है कि स्क्रिप्ट के पूर्ण पूर्ण पथ को बुलाया जा रहा है , भले ही किसी अन्य पथ से, सीधे या " source " के साथ बुलाया जाए)

#!/bin/bash
echo $0 executed

function bashscriptpath() {
  local _sp=$1
  local ascript="$0"
  local asp="$(dirname $0)"
  #echo "b1 asp '$asp', b1 ascript '$ascript'"
  if [[ "$asp" == "." && "$ascript" != "bash" && "$ascript" != "./.bashrc" ]] ; then asp="${BASH_SOURCE[0]%/*}"
  elif [[ "$asp" == "." && "$ascript" == "./.bashrc" ]] ; then asp=$(pwd)
  else
    if [[ "$ascript" == "bash" ]] ; then
      ascript=${BASH_SOURCE[0]}
      asp="$(dirname $ascript)"
    fi  
    #echo "b2 asp '$asp', b2 ascript '$ascript'"
    if [[ "${ascript#/}" != "$ascript" ]]; then asp=$asp ;
    elif [[ "${ascript#../}" != "$ascript" ]]; then
      asp=$(pwd)
      while [[ "${ascript#../}" != "$ascript" ]]; do
        asp=${asp%/*}
        ascript=${ascript#../}
      done
    elif [[ "${ascript#*/}" != "$ascript" ]];  then
      if [[ "$asp" == "." ]] ; then asp=$(pwd) ; else asp="$(pwd)/${asp}"; fi
    fi  
  fi  
  eval $_sp="'$asp'"
}

bashscriptpath H
export H=${H}

कुंजी " source " केस का पता लगाने और वास्तविक स्क्रिप्ट वापस पाने के लिए ${BASH_SOURCE[0]} करना है।


हमने मुफ्त और अनगिनत समुदाय के उपयोग के लिए realpath-lib पर अपना खुद का उत्पाद realpath-lib रखा है।

बेकार प्लग लेकिन इस बैश लाइब्रेरी के साथ आप यह कर सकते हैं:

get_realpath <absolute|relative|symlink|local file>

यह कार्य पुस्तकालय का मूल है:

function get_realpath() {

if [[ -f "$1" ]]
then 
    # file *must* exist
    if cd "$(echo "${1%/*}")" &>/dev/null
    then 
        # file *may* not be local
        # exception is ./file.ext
        # try 'cd .; cd -;' *works!*
        local tmppwd="$PWD"
        cd - &>/dev/null
    else 
        # file *must* be local
        local tmppwd="$PWD"
    fi
else 
    # file *cannot* exist
    return 1 # failure
fi

# reassemble realpath
echo "$tmppwd"/"${1##*/}"
return 0 # success

}

इसे किसी बाहरी निर्भरता की आवश्यकता नहीं है, बस बैश 4+। get_dirname , get_filename , get_stemname और फ़ंक्शंस में फ़ंक्शन भी शामिल हैं validate_path validate_realpath । यह नि: शुल्क, साफ, सरल और अच्छी तरह से प्रलेखित है, इसलिए इसका उपयोग सीखने के उद्देश्यों के लिए भी किया जा सकता है, और इसमें कोई संदेह नहीं हो सकता है। इसे प्लेटफॉर्म पर आज़माएं।

अपडेट करें: कुछ समीक्षा और परीक्षण के बाद हमने उपर्युक्त फ़ंक्शन को उस चीज़ के साथ बदल दिया है जो एक ही परिणाम प्राप्त करता है (डायरनाम का उपयोग किए बिना, केवल शुद्ध बैश) लेकिन बेहतर दक्षता के साथ:

function get_realpath() {

    [[ ! -f "$1" ]] && return 1 # failure : file does not exist.
    [[ -n "$no_symlinks" ]] && local pwdp='pwd -P' || local pwdp='pwd' # do symlinks.
    echo "$( cd "$( echo "${1%/*}" )" 2>/dev/null; $pwdp )"/"${1##*/}" # echo result.
    return 0 # success

}

इसमें पर्यावरण सेटिंग no_symlinks भी शामिल है जो भौतिक प्रणाली में no_symlinks को हल करने की क्षमता प्रदान करता है। डिफ़ॉल्ट रूप से यह symlinks बरकरार रहता है।





path