Надежный способ для скрипта bash получить полный путь к себе? [дубликат]

этот вопрос уже есть ответ здесь:

у меня есть скрипт bash, который должен знать свой полный путь. Я пытаюсь найти широко-совместимый способ сделать это, не заканчивая с относительной или симпатичный пути. Мне нужно только поддержка bash, не sh, csh и т. д.

что я нашел до сих пор:

  1. принятый ответ на "получение исходного каталога скрипта Bash изнутри" адреса, получающие путь скрипта через dirname , что хорошо, но это может вернуть относительные путь (например,.), что является проблемой, если вы хотите изменить каталоги в скрипте и путь все еще указывает на каталог скрипта. И все же ... --3--> будет частью головоломки.

  2. принятый ответ на "абсолютный путь сценария Bash с OSX" (OS X конкретный, но ответ работает независимо) дает функцию, которая будет проверять, чтобы увидеть, если выглядит относительным, и если так будет pre-pend $PWD к нему. Но результат все равно может иметь относительные биты (хотя в целом он абсолютный) - например, если скрипт t в каталоге /usr/bin и ты /usr и вы тип bin/../bin/t чтобы запустить его (да, это запутанно), вы в конечном итоге с /usr/bin/../bin как путь к каталогу скрипта. Который работает, но...

  3. на readlink решение на этой странице, который выглядит так:

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

    но readlink не POSIX и, по-видимому, решение зависит от GNU readlink где BSD не будет работать по какой-то причине (у меня нет доступа к BSD-подобной системе для проверять.)

Итак, различные способы сделать это, но у всех есть свои предостережения.

что было бы лучшим способом? Где "лучше" означает:

  • дает мне абсолютный путь.
  • извлекает фанки-биты даже при вызове запутанным способом (см. Комментарий к #2 выше). (Например, по крайней мере, умеренно канонизирует путь.)
  • полагается только на bash-isms или вещи, которые почти наверняка будут на самых популярных вкусах * системы nix (GNU / Linux, BSD и BSD-подобные системы, такие как OS X и т. д.).
  • избегает вызова внешних программ, если это возможно (например, предпочитает встроенные bash).
  • (Обновлено, спасибо за предупреждение,wich) не нужно разрешать символические ссылки (на самом деле, я бы предпочел, чтобы он оставил их в покое, но это не требование).

23 ответов


вот что я придумал (edit: плюс некоторые настройки, предоставляемые sfstewman, levigroker, Кайл Сумка и Роб Кеннеди), это, кажется, в основном соответствует моим" лучшим " критериям:

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

Это SCRIPTPATH линия кажется особенно Окольной, но нам она нужна, а не SCRIPTPATH=`pwd` для правильной обработки пробелов и символических ссылок.

обратите внимание также, что эзотерические ситуации, такие как выполнение сценария, который не исходящий из файла в доступной файловой системе вообще (что вполне возможно), не обслуживается там (или в любом из других ответов, которые я видел).


Я удивлен, что realpath команда здесь не упоминается. Насколько я понимаю, он широко переносится / портируется.

ваше начальное решение становится:

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

и оставить символические ссылки неразрешенными в соответствии с вашими предпочтениями:

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

самый простой способ, который я нашел, чтобы получить полный канонический путь в bash является использование cd и pwd:

ABSOLUTE_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"

используя ${BASH_SOURCE[0]} вместо производит такое же поведение независимо от того, вызывается ли скрипт как <name> или source <name>


Мне просто нужно было вернуться к этой проблеме сегодня и найти https://stackoverflow.com/a/246128/1034080. Он разрабатывает решение, которое я использовал в прошлом, а также.

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

в связанном ответе есть больше вариантов, например, для случая, когда сам скрипт является символической ссылкой.


получить абсолютный путь скрипта

Не использовать -f опция в readlink, поэтому должна работать в bsd / mac-osx

поддерживает

  • источник ./ script (при вызове . точка оператора)
  • абсолютный путь /путь/до/скрипта
  • относительный путь ./ script
  • / path / dir1/../директория dir2/dir3/../ script
  • при вызове из symlink
  • если ссылка вложена например) 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`]"

известный issuse

скрипт должно быть где-то на диске пусть будет по сети. если вы попытаетесь запустить этот скрипт из трубы, он не будет работать

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

Технически говоря, это не определено.
Практически говоря, нет никакого разумного способа обнаружить это. (co-process не может получить доступ к env родителя)


а как насчет:

SCRIPT_PATH=$(dirname `which `)

which печатает в stdout полный путь исполняемого файла, который был бы выполнен, когда переданный аргумент был введен в командной строке (что и содержит $0)

dirname удаляет суффикс без каталога из имени файла

следовательно, вы получаете полный путь к скрипту, независимо от того, был ли указан путь или нет.


поскольку realpath не установлен по умолчанию в моей системе Linux, для меня работает следующее:

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

$SCRIPT будет содержать реальный путь к файлу сценария и $SCRIPTPATH реальный путь к каталогу, содержащему скрипт.

перед использованием этого читать комментарии ответ.


отвечая на этот вопрос слишком поздно, но я использую:

SCRIPT=$( readlink -m $( type -p  ))      # Full path to script
BASE_DIR=`dirname ${SCRIPT}`                # Directory script is run in
NAME=`basename ${SCRIPT}`                   # Actual name of script even if linked

мы разместили наш собственный продукт realpath-lib на GitHub для свободного и свободного использования сообщества.

бесстыдный плагин, но с этой библиотекой Bash вы можете:

get_realpath <absolute|relative|symlink|local file>

эта функция является ядром библиотеки:

function get_realpath() {

if [[ -f "" ]]
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

}

он не требует каких-либо внешних зависимостей, просто Bash 4+. Также содержит функции для get_dirname, get_filename, get_stemname и validate_path validate_realpath. Это бесплатно, чисто, просто и хорошо документировано, поэтому его можно использовать и в учебных целях, и, без сомнения, можно улучшить. Попробуйте это на разных платформах.

Update: после некоторого обзора и тестирования мы заменили вышеуказанную функцию чем-то, что достигает того же результата (без использования dirname, только pure Bash), но с лучшей эффективностью:

function get_realpath() {

    [[ ! -f "" ]] && 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 это обеспечивает возможность разрешения символических ссылок на физическую систему. По умолчанию он сохраняет символические ссылки нетронутый.


вы можете попытаться определить следующую переменную:

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

или вы можете попробовать следующую функцию в bash:

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

эта функция принимает 1 аргумент. Если аргумент уже имеет абсолютный путь, выведите его как есть, иначе выведите $PWD переменная + аргумент filename (без ./ префикс).

по теме:


Ш. уступчивый образом:

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

просто: BASEDIR=$(readlink -f | xargs dirname)

нет фантазии операторов

взысканное.


возможно, принятый ответ на следующий вопрос может помочь.

как я могу получить поведение GNU readlink-f на Mac?

учитывая, что вы просто хотите канонизировать имя, которое вы получаете от объединения $PWD и $0 (предполагая, что $0 не является абсолютным для начала), просто используйте серию замен регулярных выражений по строке abs_dir=${abs_dir//\/.\//\/} и такие.

Да, я знаю, что это выглядит ужасно, но это будет работать и чистый Баш.


рассмотрение этой проблемы снова: есть очень популярное решение, на которое ссылаются в этом потоке, который имеет свое происхождение здесь:

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

Я держался подальше от этого решения из - за использования dirname-он может представлять кросс-платформенные трудности, особенно если сценарий должен быть заблокирован по соображениям безопасности. Но как чистая альтернатива Bash, как насчет использования:

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

будет ли это вариант?


легко читается? альтернатива. Игнорирует симлинки

#!/bin/bash
currentDir=$(
  cd $(dirname "")
  pwd
) 

echo -n "current "
pwd
echo script $currentDir 

принятое решение имеет неудобное (для меня), чтобы не быть "источником":
если вы позвоните из"source ../../yourScript", будет "bash"!

следующая функция (для bash >= 3.0) дает мне правильный путь, однако скрипт может быть вызван (напрямую или через source, С абсолютным или относительным путем):
(под "правильным путем" я подразумеваю полный абсолютный путь вызываемого скрипта, даже при вызове с другого пути, напрямую или с "source")

#!/bin/bash
echo  executed

function bashscriptpath() {
  local _sp=
  local ascript=""
  local asp="$(dirname )"
  #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]} чтобы вернуть фактический скрипт.


Если мы используем Bash, я считаю, что это самый удобный способ, поскольку он не требует вызовов каких-либо внешних команд:

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

попробуйте это:

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

просто для ада я сделал немного взлома скрипта, который делает вещи чисто текстуально, чисто в bash. Надеюсь, я поймал все крайние случаи. Обратите внимание, что ${var//pat/repl} то, что я упомянул в другом ответе, не работает, так как вы не можете заменить его только кратчайшим возможным совпадением, что является проблемой для замены /foo/../, например,/*/../ примет все, а не только одну запись. И поскольку эти шаблоны на самом деле не являются регулярными, я не вижу, как это можно сделать работа. Итак, вот красиво запутанное решение, которое я придумал, наслаждайтесь. ;)

кстати, дайте мне знать, если вы найдете какие-либо необработанные крайние случаи.

#!/bin/bash

canonicalize_path() {
  local path=""
  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/"

я использовал следующий подход успешно некоторое время (не на OSX, хотя), и он использует только встроенную оболочку и обрабатывает " источник foobar.sh-дело, насколько я знаю.

одна проблема с приведенным ниже (поспешно собранным) примером кода заключается в том, что функция использует $PWD, который может быть или не быть правильным во время вызова функции. Так что с этим нужно разобраться.

#!/bin/bash

function canonical_path() {
  # Handle realtive vs absolute path
  [ ${1:0:1} == '/' ] && x= || x=$PWD/
  # Change to dirname of x
  cd ${x%/*}
  # Combine new pwd with basename of x
  echo $(pwd -P)/${x##*/}
  cd $OLDPWD
}

echo $(canonical_path "${BASH_SOURCE[0]}")

type [
type cd
type echo
type pwd

еще один способ сделать это:

shopt -s extglob

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

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

echo $selfpath $selfdir

один лайнер

`dirname $(realpath )`

проще говоря, это то, что работает для меня:

MY_DIR=`dirname `
source $MY_DIR/_inc_db.sh