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


29

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

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

mypath=$(readlink -f $0)

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

Какой способ более портативный?


Ответы:


28

С 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' "$@"
}

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


Примечание: это не работает для вас .zshrc: см. Этот пост .
Брайс Гуинта

24

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

mypath=${0:a}

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

mydir=${0:a:h}

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


Милая! Я искал что-то подобное целую вечность и читал целую кучу страниц zsh, ищущих его, но мне никогда бы не пришло в голову заглянуть под «Расширение истории».
Vucar Timnärakrul

1
Эквивалент GNU readlink -fбудет лучше $0:A.
Стефан Шазелас

11

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

# The absolute, canonical ( no ".." ) path to this script
canonical=$(cd -P -- "$(dirname -- "$0")" && printf '%s\n' "$(pwd -P)/$(basename -- "$0")")

1
мне это нравится! пока что это довольно переносимо. работает в Solaris, OmniOS, Linux, Mac и даже Cygwin в Windows 2008.
Тим Кеннеди,

4
Где это не эквивалентно GNU - readlink -fэто когда сам скрипт является символической ссылкой.
Стефан Шазелас

В Ubuntu 16.04 с zsh, если я выполняю это либо напрямую, либо в подоболочке (как предложено), находясь в моем домашнем каталоге dir ( /home/ville), он печатается /home/ville/zsh.
Вилле

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 (не 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

Спасибо. хотя я думаю, что сброс $PWDможет быть излишним - вы можете просто установить его на абсолютный ток, как cd -P .. Я сомневаюсь, что это сработает в bourneshell - но оно должно работать во всех тестах, которые вы тестировали в первый раз. все равно для меня
mikeserv

@moose В какой ОС вы работаете?
Jlliagre

кто лось? что?
mikeserv

@mikeserv moose - это прохожий, который опубликовал комментарий о какой-то проблеме zshи dirnameбыстро отозвал свой комментарий ...
jlliagre

Ваш скрипт Bourne Shell не будет работать с Bourne Shell, так как Bourne Shell не использует getopt () для cd (1).
Щил

4

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

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

Кстати, это работает в любой оболочке в стиле Bourne.

Если вы имели в виду путь со всеми символическими ссылками, то это другое дело. 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Кстати, функция не обрабатывает этот случай для имен каталогов.)


1
Знаете ли вы о какой-либо lsреализации, которая искажает непечатные символы, когда вывод не идет в терминал.
Стефан Шазелас

1
@ StéphaneChazelas touch Stéphane; LC_ALL=C busybox ls Stéphane | catSt??phane(это если имя в UTF-8, latin1 дает вам один ?). Я думаю, что видел это и на старых коммерческих Unices.
Жиль "ТАК - перестать быть злым"

@ StéphaneChazelas Я исправил несколько ошибок, но они не были тщательно протестированы. Дайте мне знать, если в некоторых случаях это все равно не удается (кроме отсутствия разрешений на выполнение в некоторых каталогах, я не собираюсь иметь дело с этим крайним случаем).
Жиль "ТАК - перестань быть злым"

@ Жиль - что busyboxэто? согласно git, busybox ls с 2011 года код не менялся. Мой busybox ls- около 2013 года - этого не делает. Это одна - около 2012 - делает . Это может объяснить почему. Вы создали свой busyboxс поддержкой Unicode - чтобы включить поддержку wchar? Возможно, вы захотите попробовать, либо проверить параметры сборки в mkinitcpio busyboxпакете.
mikeserv

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

1

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

Шаг 10 из cdспецификации

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

И на pwd -P

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

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

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

ВЫХОД

/home/mikeserv/test/ln

ждать его...

cd -P . ; cd . ; cd -

ВЫХОД

/home/mikeserv/test/dir

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

Так что простого выполнения вышеупомянутого пути ${0%/*}должно быть более чем достаточно, чтобы проверить $0путь, но в случае, если $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                                    
}

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

Вызывается с аргументами или без них, и первое, что он делает, - возвращается $PWDк своему каноническому значению - при необходимости он разрешает любые ссылки в них на свои цели. Называется без аргументов и все тут; но вызывается с ними, и он будет разрешать и канонизировать путь для каждого или распечатать сообщение, stderrпочему бы и нет.

Поскольку он в основном работает в текущей оболочке, он должен иметь возможность обрабатывать список аргументов любой длины. Он также ищет $_zdlmпеременную (которую онunset также просматривает, когда проходит через нее) и печатает ее C-escape-значение сразу справа от каждого из своих аргументов, за каждым из которых всегда следует также один \nсимвол ewline.

Он сильно меняет каталог, но, кроме установки его канонического значения, он не влияет $PWD, хотя $OLDPWDна него нельзя рассчитывать, когда он закончится.

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

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

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

cd -...ls...echo /

Он отбрасывает слева от lsвыходных данных столько, сколько должен полностью содержать имя ссылки и строку ->. Хотя я сначала пытался избежать этого, shiftи $IFSоказалось, что это самый надежный метод, насколько я могу судить. Это то же самое, что делает Жиль_Плохая_мани_челка - и это хорошо сделано.

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

ВЫХОД

/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/

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

ls
dir/  file  file?  folder/  link@  ln@  script*  script3@  script4@

zdlm=\\0 zpath * | cat -A

ВЫХОД

/home/mikeserv/test/dir/^@$               
/home/mikeserv/test/file^@$
/home/mikeserv/test/file$
^@$
/home/mikeserv/test/folder/^@$
/home/mikeserv/test/file$               #'link' -> 'file\n'
^@$
/home/mikeserv/test/dir/^@$             #'ln' -> './dir'
/home/mikeserv/test/script^@$
/home/mikeserv/test/dir/script.sh^@$    #'script3' -> './dir/script.sh'
/home/mikeserv/test/dir/script.sh^@$    #'script4' -> '/tmp/script' -> ...

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.