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

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

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

  1. Принятый ответ на Получение исходного каталога скрипта Bash из адресов, получающих путь к скрипту через dirname $0, что нормально, но это может вернуть относительный путь (как .), Что является проблемой, если вы хотите изменить каталоги в скрипте и путь к нему все еще указывает на каталог скрипта. Тем не менее, dirname будет частью головоломки.

  2. Принятый ответ на абсолютный путь Bash-скрипта с OS X (специфично для OS X, но ответ работает независимо) дает функцию, которая проверит, смотрится ли $0 относительно, и если да, то будет предварительно ожидать $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 $0)
    # 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).
  • (Обновлено, спасибо за заголовок, который) Не нужно разрешать символические ссылки (на самом деле, я бы предпочел, чтобы он оставил их в покое, но это не требование).

Ответы

Ответ 1

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

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

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

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

Ответ 2

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

Ваше начальное решение будет:

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

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

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

Ответ 3

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

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

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

Ответ 5

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

Он не использует -f в readlink, и поэтому должен работать на BSD/Mac OS X.

опоры

  • source./script (при вызове оператором . dot)
  • Абсолютный путь/путь/к/скрипту
  • Относительный путь вроде. /script
  • /path/dir1/../dir2/dir3/../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

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

Ответ 6

Использование:

SCRIPT_PATH=$(dirname 'which $0')

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

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

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

Ответ 7

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

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

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

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

Ответ 8

Легко читать? Ниже приводится альтернатива. Игнорирует символические ссылки

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

echo -n "current "
pwd
echo script $currentDir

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

ORIGIN=$(dirname $(readlink -f $0))

Ответ 9

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

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

Ответ 10

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

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

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

}

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

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

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, которая предоставляет возможность разрешать символические ссылки на физическую систему. По умолчанию он сохраняет неизменяемые символические ссылки.

Ответ 11

Просто:

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

Необычные операторы не нужны.

Ответ 12

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

CWD="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"

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

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

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

Связанные с:

Ответ 13

Bourne shell (sh) совместимый способ:

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

Ответ 14

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

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

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

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

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

Ответ 15

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

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

Ответ 16

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

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

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

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

Ответ 17

Принятое решение имеет неудобное (для меня) не "исходное":
если вы называете это "source ../../yourScript", $0 будет "bash"!

Следующая функция (для bash >= 3.0) дает мне правильный путь, однако script может быть вызван (напрямую или через source с абсолютным или относительным путем):
(по "правильному пути", я имею в виду полный абсолютный путь script, который называется, даже при вызове с другого пути, напрямую или с помощью "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]}, чтобы вернуть фактический script.

Ответ 18

Попробуйте следующее:

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

Ответ 19

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

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

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

#!/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/"

Ответ 20

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

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

#!/bin/bash

function canonical_path() {
  # Handle relative vs absolute path
  [ ${1:0:1} == '/' ] && x=$1 || x=$PWD/$1
  # 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

Ответ 21

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

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

Ответ 22

Один вкладыш

`dirname $(realpath $0)`

Ответ 23

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

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