Надежный способ для скрипта 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).
  • (Обновлено, спасибо за wich) Не нужно разрешать символические ссылки (на самом деле, я бы предпочел, чтобы он оставил их в покое, но это не является обязательным требованием).

23 ответа

Решение

Вот то, что я придумал (правка: плюс некоторые sfstewman, предоставленные sfstewman, levigroker, Kyle Strand и Rob Kennedy), которые в основном соответствуют моим "лучшим" критериям:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

опоры

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

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

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

SCRIPT_PATH=$(dirname `which $0`)

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

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

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

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

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

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

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

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

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

echo -n "current "
pwd
echo script $currentDir

Просто:

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

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

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

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

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

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

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

Связанные с:

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

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

Мы разместили наш собственный продукт 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_pathvalidate_realpath, Он бесплатный, чистый, простой и хорошо документированный, поэтому его можно использовать и в учебных целях, и, без сомнения, его можно улучшить. Попробуйте на разных платформах.

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

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

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

Один лайнер

`dirname $(realpath $0)`

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

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

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

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

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

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

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

Принятое решение имеет неудобство (для меня), чтобы не быть "исходным":
если вы называете это из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]} чтобы вернуть реальный сценарий.

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

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

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

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

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

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

Некоторое время я успешно использовал следующий подход (но не в 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

Просто, черт возьми, я немного взломал скрипт, который делает вещи чисто текстовыми, чисто в 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/"

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

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

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

MY_DIR=`dirname $0`
source $MY_DIR/_inc_db.sh
Другие вопросы по тегам