Портативний спосіб отримати абсолютний шлях сценарію?


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

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


Примітка: це не працює для вашого .zshrc: дивіться замість цієї публікації .
Брайс Гінта

24

У zsh ви можете зробити наступне:

mypath=${0:a}

Або отримати каталог, в якому знаходиться сценарій:

mydir=${0:a:h}

Джерело: головна сторінка zshexpn (1), розділ РОЗШИРЕННЯ ІСТОРІЇ, підрозділ Модифікатори (або просто info -f zsh -n Modifiers).


Солодке! Я шукав щось подібне до цього віками і читав цілу купу zsh manpages, які шукають його, але мені ніколи б не прийшло в голову заглянути в розділі "Розширення історії".
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 .. Я сумніваюсь, що це спрацювало б у бурнешках - але воно повинно працювати у всіх тих, кого ви перевірили першими. все одно для мене.
mikeserv

@moose Яку ОС ти працюєш?
jlliagre

хто лось? що?
mikeserv

@mikeserv лось перехожий , який розмістив коментар про яку - то проблеми з zshі dirnameале швидко вийти хір / її коментар ...
jlliagre

Ваш сценарій Bourne Shell не працюватиме з Bourne Shell, оскільки Bourne Shell не використовує getopt () для CD (1).
schily

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Функція, до речі, не обробляє цей випадок для імен каталогів.)


1
Чи знаєте ви про будь-яку lsреалізацію, яка керує символами, які не надруковані, коли вихід не йде до терміналу.
Стефан Шазелас

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

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

@Gilles - що busyboxце? за інформацією git busybox ls , не було зміни коду з 2011 року. Мій busybox ls- приблизно у 2013 році - цього не робить. Це одна - близько 2012 - робить . Це може пояснити, чому. Ви створили свою busyboxпідтримку Unicode - щоб включити підтримку wchar? Ви можете хотіти, щоб перевірити варіанти збирання в mkinitcpio busyboxпакеті.
mikeserv

Жил - я вважаю, що я спочатку неправильно оцінив цю відповідь - або хоча б її частину. Хоча я твердо вірю, що ваша мантра назви файлів файлів буде абсолютно помилковою, але, безумовно, ваша погане_масте_посилання дуже добре зроблене. Якщо ви зробите мені доброту внесення змін - будь-яка редакція, - і пінгінг мені згодом, я хотів би змінити свій голос з цього приводу.
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проходить, коли вона проходить) і надрукує її значення, що уникнуло С, відразу ж праворуч від кожного з його аргументів, за кожним з яких завжди також йде один \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.