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

Что такое переносимый способ для скрипта (zsh) для определения его абсолютного пути?

В Linux я использую что-то вроде

mypath=$(readlink -f $0)

... но это не переносимо. (Например, readlink на darwin не распознает -f, и не имеет эквивалента.) (Кроме того, использование readlink для этого, по общему признанию, довольно неясно выглядящего взлома. )

Что более переносимо?

25 голосов | спросил kjo 21 Mayam13 2013, 01:30:16

7 ответов


23

С помощью zsh это просто:

mypath=$0:A

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

Как и для переносимости, вы можете обратиться к perl:

abs_path() {
  perl -MCwd -le '
    for (@ARGV) {
      if ($p = Cwd::abs_path $_) {
        print $p;
      } else {
        warn "abs_path: $_: $!\n";
        $ret = 1;
      }
    }
    exit $ret' "[email protected]"
}

Это будет больше похоже на readlink -f GNU, чем realpath() (GNU readlink -e), в котором он не будет жаловаться, если файл не существует до тех пор, пока его dirname делает.

ответил Stéphane Chazelas 11 J0000006Europe/Moscow 2014, 14:17:19
20

В zsh вы можете сделать следующее:

mypath=${0:a}

Или, чтобы получить каталог, в котором находится скрипт:

mydir=${0:a:h}

Источник: zshexpn (1) справочная страница, раздел ИСТОРИЯ РАСШИРЕНИЯ, подразделы Модификаторы (или просто info -f zsh -n Modifiers).

ответил mrmenken 16 FebruaryEurope/MoscowbSun, 16 Feb 2014 20:22:30 +0400000000pmSun, 16 Feb 2014 20:22:30 +040014 2014, 20:22:30
11

Я использую это уже несколько лет:

# The absolute, canonical ( no ".." ) path to this script
canonical=$(cd -P -- "$(dirname -- "$0")" && printf '%s\n' "$(pwd -P)/$(basename -- "$0")")
ответил user17591 21 Maypm13 2013, 20:25:17
8

Этот синтаксис должен быть переносимым для любого интерпретатора стиля оболочки Bourne (проверен с помощью bash, ksh88, ksh93, zsh, mksh, dash и busybox sh):

mypath=$(exec 2>/dev/null;cd -- $(dirname "$0"); unset PWD; /usr/bin/pwd || /bin/pwd || pwd)
echo mypath=$mypath

Эта версия добавляет совместимость с устаревшей AT & T Bourne shell (non POSIX):

mypath=`dirname "$0"`
mypath=`exec 2>/dev/null;(cd -- "$mypath") && cd -- "$mypath"|| cd "$mypath"; unset PWD; /usr/bin/pwd || /bin/pwd || pwd`
echo mypath=$mypath
ответил jlliagre 21 Mayam13 2013, 03:32:32
4

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

case $0 in
  /*) mypath=$0;;
  *) mypath=$PWD/$0;;
esac

Это работает в любой оболочке в стиле Бурна, кстати.

Если вы имели в виду путь со всеми разрешенными символическими ссылками, это другое дело. readlink -f работает на Linux (за исключением некоторых урезанных систем BusyBox), FreeBSD, NetBSD, OpenBSD и Cygwin, но не на OS /X, AIX, HP /UX или Solaris. Если у вас есть readlink, вы можете вызвать его в цикле:

realpath () {
  [ -e "$1" ] || return
  case $1 in
    /*) :;;
    *) set "$PWD/$1";;
  esac
  while [ -L "$1" ]; do
    set "${1%/*}" "$(readlink "$1")"
    case $2 in
      /*) set "$2";;
      *) if [ -z "$1" ]; then set "/$2"; else set "$(cd "$1" && pwd -P)/$2"; fi;;
    esac
  done
  case $1 in
    */.|*/..) set "$(cd "$1" && pwd -P)";;
    */./*|*/../*) set "$(cd "${1%/*}" && pwd -P)/${1##*/}"
  esac
  realpath=$1
}

Если у вас нет readlink, вы можете приблизиться к нему с помощью ls -n, но это работает только в том случае, если ls не искажает любой непечатаемый символ в имени файла .

poor_mans_readlink () {
  if [ -L "$1" ]; then
    set -- "$1" "$(LC_ALL=C command ls -n -- "$2"; echo z)"
    set -- "${2%??}"
    set -- "${2#*"$1 -> "}"
  fi
  printf '%s\n' "$1"
}

(Дополнительный z в том случае, если цель ссылки заканчивается в новой строке, которая в противном случае заменила бы команду. realpath не обрабатывает этот случай для имен каталогов, кстати.)

ответил Gilles 21 Mayam13 2013, 03:00:30
1

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

Шаг 10 из cd

  

Если используется опция -P, $PWD должна быть установлена ​​строка, которая будет выводиться с помощью pwd -P. Если для нового каталога или любого родителя этого каталога недостаточно разрешения для определения текущего рабочего каталога, значение $PWD переменная среды не указана.

И на pwd -P

  

Путь, записанный на стандартный вывод, не должен содержать компонентов, относящихся к файлам символической ссылки типа. Если существует несколько путей, которые утилита pwd могла бы записываться на стандартный вывод, один начинается с символа одиночного /косой черты и одного или нескольких, начиная с two /slash characters, тогда он должен написать путь, начинающийся с символа single /slash. Имя пути не должно содержать лишних /косой черты после следующих символов: один или два символа.

Это потому, что cd -P должен установить текущий рабочий каталог на pwd -P должен в противном случае распечатать и , чтобы cd - должен был напечатать $OLDPWD, что работает следующее:

mkdir ./dir
ln -s ./dir ./ln
cd ./ln ; cd . ; cd -

OUTPUT

/home/mikeserv/test/ln

дождитесь его ...

cd -P . ; cd . ; cd -

OUTPUT

/home/mikeserv/test/dir

И когда я печатаю с помощью cd - Я печатаю $OLDPWD. cd устанавливает $PWD, как только I ---- +: = 20 =: + ---- cd -P . теперь является абсолютным путем к ---- +: = 22 =: + ---- - поэтому мне не нужны никакие другие переменные. И на самом деле мне даже не нужен конечный $PWD, но существует определенное поведение сброса / до . в интерактивной оболочке, когда $PWD не имеет аналогов. Так что это хорошая привычка развиваться.

Так что просто делать вышеуказанное на пути в $HOME должно быть более чем достаточно, чтобы проверить cd, но в случае, когда ${0%/*} сам является софт-ссылкой , вы, вероятно, не можете сменить каталог в нем, к сожалению.

Вот функция, которая будет обрабатывать это:

$0

Он стремится сделать столько, сколько мог бы в текущей оболочке - без вызова подоболочки - хотя для ошибок и программных ссылок, которые не указывают на каталоги, существуют подсели. Это зависит от совместимой с POSIX оболочки и POSIX-совместимого $0, а также чистого zpath() { cd -P . || return _out() { printf "%s$_zdlm\n" "$PWD/${1##*/}"; } _cd() { cd -P "$1" ; } >/dev/null 2>&1 while [ $# -gt 0 ] && _cd . do if _cd "$1" then _out elif ! [ -L "$1" ] && [ -e "$1" ] then _cd "${1%/*}"; _out "$1" elif [ -L "$1" ] then ( while set -- "${1%?/}"; _cd "${1%/*}"; [ -L "${1##*/}" ] do set " $1" "$(_cd -; ls -nd -- "$1"; echo /)" set -- "${2#*"$1" -> }" done; _out "$1" ); else ( PS4=ERR:\ NO_SUCH_PATH; set -x; : "$1" ) fi; _cd -; shift; done unset -f _out _cd; unset -v _zdlm } . Он по-прежнему будет функционировать просто отлично без последнего, хотя он может перезаписать затем ls некоторые текущие функции оболочки в этом случае. В общем, все эти зависимости должны быть достаточно надежно доступны на машине Unix.

Вызывается с аргументами или без них, первое, что он делает, - это сбросить _function() к его каноническому значению - он разрешает любые ссылки в них для своих целей как надо. Вызывается без аргументов, и это все; но вызывается с ними, и он разрешит и canonicalize путь для каждого или еще напечатать сообщение unset почемунет.

Поскольку он в основном работает в текущей оболочке, он должен иметь возможность обрабатывать список аргументов любой длины. Он также ищет переменную $PWD (которая также stderr s, когда он завершен) и сразу же распечатывает свое C-экранированное значение справа от каждого из своих аргументов, каждый из которых всегда сопровождается одним $_zdlm символ сети.

Он сильно изменяет каталог, но, кроме установки его канонического значения, он не влияет на unset, хотя \n ни в коем случае нельзя рассчитывать, когда он пройдет.

Он пытается закрыть каждый из своих аргументов, как только это возможно. Сначала он пытается $PWD в $OLDPWD. Если он может напечатать канонический путь аргумента к cd. Если он не может проверить, что $1 существует и не является мягкой ссылкой. Если true, он печатает.

Таким образом, он обрабатывает любой аргумент типа файла, который оболочка имеет разрешения для адреса, если stdout не является символической ссылкой, которая не указывает в каталог. В этом случае он вызывает цикл $1 в подоболочке.

Он вызывает $1, чтобы прочитать ссылку. Текущая директория сначала должна быть изменена на ее начальное значение, чтобы надежно обрабатывать любые пути референта, и поэтому в подзаголовке подстановки команды функция выполняет:

while

Он выводит слева слева от ls вывод только для того, чтобы полностью содержать имя ссылки и строку cd -...ls...echo / . В то время как я сначала пытался избежать этого с помощью ls и -> выясняется, что это самый надежный метод, как я могу понять. Это то же самое, что делает poor_mans_readlink Жильса - и это хорошо сделано.

Он повторит этот процесс в цикле, пока имя файла, возвращаемое из shift, определенно не является мягкой ссылкой. В этот момент он канонизирует этот путь по-прежнему с помощью $IFS, затем печатает.

Пример использования:

ls

OUTPUT

cd

Или возможно ...

zpath \
    /tmp/script \   #symlink to $HOME/test/dir/script.sh
    ln \            #symlink to ./dir/
    ln/nl \         #symlink to ../..
    /dev/fd/0 \     #currently a here-document like : dash <<\HD
    /dev/fd/1 \     #(zlink) | dash
    file \          #regular file                                             
    doesntexist \   #doesnt exist
    /dev/disk/by-path/pci-0000:00:16.2-usb-0:3:1.0-scsi-0:0:0:0 \
    /dev/./././././././null \
    . ..      

OUTPUT

/home/mikeserv/test/dir/script.sh
/home/mikeserv/test/dir/
/home/mikeserv/test/
/tmp/zshtpKRVx (deleted)
/proc/17420/fd/pipe:[1782312]
/home/mikeserv/test/file
ERR: NO_SUCH_PATH: doesntexist
/dev/sdd
/dev/null
/home/mikeserv/test/
/home/mikeserv/
ответил mikeserv 27 PM000000100000002431 2014, 22:40:24
0

как насчет симпатической однострочной линии, когда существует возможность использовать python для предотвращения переопределения алгоритма?

function readlink { python -c "import os.path; print os.path.realpath('$1')"; }

то же самое, что https://stackoverflow.com/a/7305217

ответил Antonio Daniel Rodriguez 29 J000000Saturday17 2017, 04:01:43

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132