Скрипт оболонки Unix з’ясує, в якому каталозі знаходиться файл сценарію?


510

В основному мені потрібно запустити скрипт із шляхами, пов’язаними з розташуванням файлу скрипта оболонки, як я можу змінити поточний каталог на той самий каталог, де знаходиться файл сценарію?


12
Це справді дублікат? Це питання стосується "скрипту оболонки Unix", іншого конкретно про Bash.
michaeljt

2
@BoltClock: Це питання було неправильно закрито. Пов'язане питання стосується Баша. Це питання стосується програмування оболонок Unix. Зауважте, що прийняті відповіді зовсім інші!
Дітріх Епп

@Dietrich Epp: Ти маєш рацію. Здається, що вибір запитувача прийняв відповідь і додавання тегу [bash] (можливо, у відповідь на це) призвело до того, що я позначив це питання як дублікат у відповідь на прапор.
BoltClock


Відповіді:


567

У Bash ви повинні отримати те, що вам потрібно так:

#!/usr/bin/env bash

BASEDIR=$(dirname "$0")
echo "$BASEDIR"

17
Це не працює, якщо ви викликали скрипт через символічне посилання в іншому каталозі. Щоб зробити цю роботу, вам потрібно також скористатися readlink(див. Відповідь Ал. Нижче)
AndrewR

44
У bash це безпечніше використовувати $BASH_SOURCEзамість цього $0, оскільки $0не завжди міститься шлях викликаного сценарію, наприклад, коли "джерело" сценарію.
mklement0

2
$BASH_SOURCEє специфічним для Bash, питання взагалі про скрипт оболонки.
Ха-Дуонг Нгуен

7
@auraham: CUR_PATH=$(pwd)або pwdповернути поточний каталог (який не повинен бути батьківським сценарієм dir)!
Андреас Дітріх

5
Я спробував метод @ mklement0, який рекомендував $BASH_SOURCE, і він повертає те, що мені потрібно. Мій скрипт викликається з іншого сценарію і $0повертається ., $BASH_SOURCEповертаючи правий підкаталог (у моєму випадку scripts).
Девід Ріссато Крус

401

Оригінальний пост містить рішення (ігноруйте відповіді, вони не додають нічого корисного). Цікава робота виконується згаданою командою unix readlinkз опцією -f. Працює, коли сценарій викликається як абсолютним, так і відносним шляхом.

Для баш, ш, кш:

#!/bin/bash 
# Absolute path to this script, e.g. /home/user/bin/foo.sh
SCRIPT=$(readlink -f "$0")
# Absolute path this script is in, thus /home/user/bin
SCRIPTPATH=$(dirname "$SCRIPT")
echo $SCRIPTPATH

Для tcsh, csh:

#!/bin/tcsh
# Absolute path to this script, e.g. /home/user/bin/foo.csh
set SCRIPT=`readlink -f "$0"`
# Absolute path this script is in, thus /home/user/bin
set SCRIPTPATH=`dirname "$SCRIPT"`
echo $SCRIPTPATH

Дивіться також: https://stackoverflow.com/a/246128/59087


12
Примітка: Не всі системи мають readlink. Ось чому я рекомендував використовувати pushd / popd (вбудовані для bash).
док

23
Можливість -fзробити readlinkщось інше на OS X (Lion) та, можливо, на BSD. stackoverflow.com/questions/1055671 / ...
Ergwun

9
Щоб уточнити коментар @ Ergwun: -fвзагалі не підтримується в OS X (як у Lion); там ви можете або відмовитись від -fвирішення, щонайменше, на одному рівні непрямості, наприклад pushd "$(dirname "$(readlink "$BASH_SOURCE" || echo "$BASH_SOURCE")")", або можете прокатати свій власний рекурсивний скрипт, що слідує за символьними посиланнями, як продемонстровано у зв’язаному дописі.
mklement0

2
Я досі не розумію, навіщо ОП потребуватиме абсолютний шлях. Звітування "." має працювати добре, якщо ви хочете отримати доступ до файлів щодо шляху скриптів, і ви назвали такий сценарій, як ./myscript.sh
Stefan Haberl

10
@StefanHaberl Я думаю, що це буде проблемою, якщо ви запустили скрипт, тоді як ваш теперішній робочий каталог відрізнявся від місця розташування сценарію (наприклад sh /some/other/directory/script.sh), у цьому випадку .буде ваш pwd, а не/some/other/directory
Jon z

51

Попередній коментар до відповіді сказав це, але легко пропустити серед усіх інших відповідей.

При використанні bash:

echo this file: "$BASH_SOURCE"
echo this dir: "$(dirname "$BASH_SOURCE")"

Довідник по Bash, 5.2 Змінні


3
Тільки цей працює зі сценарієм у середовищі, більшість з них не працюють. Дякую тобі!
липня

dirname "$BASH_SOURCE"Натомість слід використовувати для обробки пробілів у $ BASH_SOURCE.
Mygod

1
Більш явним способом друку каталогу буде, згідно з інструментом ShellCheck, бути: "$ (dirname" $ ​​{BASH_SOURCE {0}} ")", тому що BASH_SOURCE - це масив, і без підпису перший елемент приймається за замовчуванням. .
Адріан М.

1
@AdrianM. , ви хочете дужки, а не дужки, для індексу:"$(dirname "${BASH_SOURCE[0]}")"
Hashbrown

Недоцільно для мене..друкує лише крапку (тобто поточний каталог, мабуть)
JL_SO

40

Якщо припустити, що ви використовуєте bash

#!/bin/bash

current_dir=$(pwd)
script_dir=$(dirname $0)

echo $current_dir
echo $script_dir

Цей скрипт повинен надрукувати каталог, у якому ви перебуваєте, а потім каталог, у якому знаходиться скрипт. Наприклад, при виклику з нього зі /скриптом у /home/mez/ньому виводиться

/
/home/mez

Пам'ятайте, що при призначенні змінних з виводу команди введіть команду $(і )- або ви не отримаєте потрібного виводу.


3
Це не спрацює, коли я викликаю сценарій із поточного режиму.
Ерік Ван

1
@EricWang Ви завжди в поточному каталозі.
ctrl-alt-delor

Для мене $ current_dir - це справді шлях, з якого я кличу сценарій. Однак $ script_dir не є режисером сценарію, це лише крапка.
Майкл

31

Якщо ви використовуєте bash ....

#!/bin/bash

pushd $(dirname "${0}") > /dev/null
basedir=$(pwd -L)
# Use "pwd -P" for the path without links. man bash for more info.
popd > /dev/null

echo "${basedir}"

4
Ви можете замінити pushd/ popdна cd $(dirname "${0}")і cd -змусити його працювати на інших оболонках, якщо вони мають pwd -L.
док

чому б ви тут використовували pushd і popd?
qodeninja

1
Тому мені не потрібно зберігати оригінальний каталог у змінній. Це шаблон, який я дуже багато використовую у функціях і таких. Це дуже добре гніздиться, що добре.
docwhat

Він все ще зберігається в пам'яті - у змінній - незалежно від того, на яку зміну посилається ваш сценарій чи ні. Крім того, я вважаю, що вартість виконання pushd та popd набагато перевищує заощадження від створення локальної змінної Bash у вашому сценарії, як в циклах процесора, так і в читанні.
ingyhere

20

Як пропонує Марко:

BASEDIR=$(dirname $0)
echo $BASEDIR

Це працює, якщо ви не виконаєте скрипт з того самого каталогу, де знаходиться скрипт, і в цьому випадку ви отримаєте значення "."

Щоб обійти цю проблему, використовуйте:

current_dir=$(pwd)
script_dir=$(dirname $0)

if [ $script_dir = '.' ]
then
script_dir="$current_dir"
fi

Тепер ви можете використовувати змінну current_dir у всьому сценарії, щоб звернутися до каталогу сценаріїв. Однак це все ще може мати проблему символьного посилання.


20

Найкраща відповідь на це питання відповіла тут:
Отримання вихідного каталогу сценарію Bash зсередини

І це:

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

Однокласник, який дасть вам повне ім'я каталогу скрипта, незалежно від того, звідки він викликається.

Щоб зрозуміти, як це працює, ви можете виконати такий сценарій:

#!/bin/bash

SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
  TARGET="$(readlink "$SOURCE")"
  if [[ $TARGET == /* ]]; then
    echo "SOURCE '$SOURCE' is an absolute symlink to '$TARGET'"
    SOURCE="$TARGET"
  else
    DIR="$( dirname "$SOURCE" )"
    echo "SOURCE '$SOURCE' is a relative symlink to '$TARGET' (relative to '$DIR')"
    SOURCE="$DIR/$TARGET" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
  fi
done
echo "SOURCE is '$SOURCE'"
RDIR="$( dirname "$SOURCE" )"
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
if [ "$DIR" != "$RDIR" ]; then
  echo "DIR '$RDIR' resolves to '$DIR'"
fi
echo "DIR is '$DIR'"


10

Давайте зробимо це POSIX oneliner:

a="/$0"; a=${a%/*}; a=${a#/}; a=${a:-.}; BASEDIR=$(cd "$a"; pwd)

Тестували на багатьох сумісних з Борном оболонках, включаючи BSD.

Наскільки я знаю, я є автором, і я розміщую його у відкритому доступі. Для отримання додаткової інформації дивіться: https://www.jasan.tk/posts/2017-05-11-posix_shell_dirname_replacement/


1
як написано, cd: too many argumentsякщо пробіли в шляху, і повертається $PWD. (очевидно виправлення, але тільки показує, скільки крайових справ є насправді)
michael

1
Був би піднятий. За винятком коментаря @michael про те, що він не має пробілів у шляху ... Чи є виправлення для цього?
спектер

1
@spechter так, для цього є виправлення. Дивіться оновлений jasan.tk/posix/2017/05/11/posix_shell_dirname_replacement
Ján Sáreník

@Der_Meister - будьте точнішими. Або напишіть (і, можливо, зашифруйте) електронний лист Jasan на Jasan.tk
Ján Sáreník

2
ln -s / home / der / 1 / test / home / der / 2 / test && / home / der / 2 / test => / home / der / 2 (замість цього повинен показувати шлях до оригінального сценарію)
Der_Meister


9

Якщо ви хочете отримати фактичний каталог скриптів (незалежно від того, ви викликаєте сценарій за допомогою символьного посилання чи безпосередньо), спробуйте:

BASEDIR=$(dirname $(realpath "$0"))
echo "$BASEDIR"

Це працює як на Linux, так і на macOS. Я не міг бачити, щоб хтось тут згадував realpath. Не впевнений, чи є у цього підходу якісь недоліки.

на macOS вам потрібно встановити, coreutilsщоб використовувати realpath. Наприклад: brew install coreutils.


6

ВСТУП

Ця відповідь виправляє дуже ламану, але шокуючу відповідь цієї теми (написав TheMarko):

#!/usr/bin/env bash

BASEDIR=$(dirname "$0")
echo "$BASEDIR"

ЧОМУ ВИКОРИСТОВУВАННЯ dirname "$ 0" НА ВЛАСНІЙ НЕ ПРАЦЮЄ?

dirname $ 0 буде працювати, лише якщо користувач запустить скрипт дуже специфічним чином. Мені вдалося знайти декілька ситуацій, коли ця відповідь не відповідає і руйнує сценарій.

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

dirname "$0"

$ 0 являє собою першу частину команди, що викликає скрипт (це в основному введена команда без аргументів:

/some/path/./script argument1 argument2

$ 0 = "/ деякі / шлях /./ скрипт"

dirname в основному знаходить останнє / у рядку і обрізає його там. Отже, якщо ви робите:

  dirname /usr/bin/sha256sum

ви отримаєте: / usr / bin

Цей приклад добре працює, оскільки / usr / bin / sha256sum - це правильно відформатований шлях, але

  dirname "/some/path/./script"

не буде працювати добре, і дасть вам:

  BASENAME="/some/path/." #which would crash your script if you try to use it as a path

Скажіть, ви перебуваєте в тому ж режимі, що і ваш сценарій, і ви запускаєте його за допомогою цієї команди

./script   

$ 0 у цій ситуації буде ./script та dirname $ 0 дадуть:

. #or BASEDIR=".", again this will crash your script

Використання:

sh script

Без введення повного шляху також буде дано BASEDIR = ".

Використання відносних каталогів:

 ../some/path/./script

Дає dirname $ 0:

 ../some/path/.

Якщо ви перебуваєте в каталозі / some і ви викликаєте скрипт таким чином (зверніть увагу на відсутність / на початку, знову ж відносний шлях):

 path/./script.sh

Ви отримаєте це значення для dirname $ 0:

 path/. 

і ./path/./script (інша форма відносного шляху) дає:

 ./path/.

Єдині дві ситуації, в яких базується $ 0 , - якщо користувач використовує sh або touch, щоб запустити сценарій, оскільки обидва отримають $ 0:

 $0=/some/path/script

який дасть вам шлях, який ви можете використовувати з dirname.

РІШЕННЯ

Ви маєте обліковий запис і виявляти кожну з вищезазначених ситуацій і застосовувати виправлення, якщо воно виникне:

#!/bin/bash
#this script will only work in bash, make sure it's installed on your system.

#set to false to not see all the echos
debug=true

if [ "$debug" = true ]; then echo "\$0=$0";fi


#The line below detect script's parent directory. $0 is the part of the launch command that doesn't contain the arguments
BASEDIR=$(dirname "$0") #3 situations will cause dirname $0 to fail: #situation1: user launches script while in script dir ( $0=./script)
                                                                     #situation2: different dir but ./ is used to launch script (ex. $0=/path_to/./script)
                                                                     #situation3: different dir but relative path used to launch script
if [ "$debug" = true ]; then echo 'BASEDIR=$(dirname "$0") gives: '"$BASEDIR";fi                                 

if [ "$BASEDIR" = "." ]; then BASEDIR="$(pwd)";fi # fix for situation1

_B2=${BASEDIR:$((${#BASEDIR}-2))}; B_=${BASEDIR::1}; B_2=${BASEDIR::2}; B_3=${BASEDIR::3} # <- bash only
if [ "$_B2" = "/." ]; then BASEDIR=${BASEDIR::$((${#BASEDIR}-1))};fi #fix for situation2 # <- bash only
if [ "$B_" != "/" ]; then  #fix for situation3 #<- bash only
        if [ "$B_2" = "./" ]; then
                #covers ./relative_path/(./)script
                if [ "$(pwd)" != "/" ]; then BASEDIR="$(pwd)/${BASEDIR:2}"; else BASEDIR="/${BASEDIR:2}";fi
        else
                #covers relative_path/(./)script and ../relative_path/(./)script, using ../relative_path fails if current path is a symbolic link
                if [ "$(pwd)" != "/" ]; then BASEDIR="$(pwd)/$BASEDIR"; else BASEDIR="/$BASEDIR";fi
        fi
fi

if [ "$debug" = true ]; then echo "fixed BASEDIR=$BASEDIR";fi

4

Цей однолінійний вказівник повідомляє, де знаходиться сценарій оболонки, не має значення, чи ви запустили його, чи якщо ви його отримали . Крім того, він вирішує будь-які символічні посилання, якщо це так:

dir=$(dirname $(test -L "$BASH_SOURCE" && readlink -f "$BASH_SOURCE" || echo "$BASH_SOURCE"))

До речі, я думаю, ви використовуєте / bin / bash .


2

Стільки відповідей, всі правдоподібні, кожен з профі та кон та злегка різними цілями (які, мабуть, слід зазначити для кожного). Ось ще одне рішення, яке відповідає основній меті - як зрозуміти, так і працювати в усіх системах, на всіх bash (жодних припущень щодо bash-версій readlinkчи pwdваріантів), і розумно виконує те, що ви очікуєте, що відбудеться (наприклад, вирішення символьних посилань - це цікава проблема, але зазвичай це не те, що вам потрібно?

Кожен компонент зберігається в окремій змінній, яку можна використовувати окремо:

# script path, filename, directory
PROG_PATH=${BASH_SOURCE[0]}      # this script's name
PROG_NAME=${PROG_PATH##*/}       # basename of script (strip path)
PROG_DIR="$(cd "$(dirname "${PROG_PATH:-$PWD}")" 2>/dev/null 1>&2 && pwd)"


-4

Це повинно зробити трюк:

echo `pwd`/`dirname $0`

Це може виглядати некрасиво, залежно від того, як його викликали і cwd, але слід дістати вас туди, куди вам потрібно піти (або ви можете налаштувати струну, якщо вам все одно, як вона виглядає).


1
Проблема втечі stackoverflow тут: вона, безумовно, повинна виглядати так: `pwd`/`dirname $0`але все ж може не вдатися до посилань
Андреас Дітріх
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.