Unix shell script выяснит, в каком каталоге находится файл скрипта? - PullRequest
420 голосов
/ 28 октября 2008

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

Ответы [ 16 ]

2 голосов
/ 09 июня 2018

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

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

Кстати, я полагаю, вы используете / bin / bash .

2 голосов
/ 22 апреля 2018

Введение

Этот ответ исправляет очень сломленный, но шокирующе лучший голос этой ветки (написанный 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

Дает имя $ 0 из:

 ../some/path/.

Если вы находитесь в каталоге / some и вызываете скрипт таким образом (обратите внимание на отсутствие / в начале, снова относительный путь):

 path/./script.sh

Вы получите это значение для dirname $ 0:

 path/. 

и ./path/./script (еще одна форма относительного пути) дает:

 ./path/.

Единственные две ситуации, когда basedir $ 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
2 голосов
/ 29 июля 2017

Так много ответов, все правдоподобно, каждый с за и против и слегка отличающимися целями (которые, вероятно, должны быть указаны для каждого). Вот еще одно решение, которое отвечает основной задаче: быть понятным и работать во всех системах, на всех 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)"
2 голосов
/ 19 декабря 2012

Вдохновленный ответом голубоглазого

read < <(readlink -f $0 | xargs dirname)
cd $REPLY
1 голос
/ 02 апреля 2019

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

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

Это работает как на Linux, так и на MacOS. Я не видел здесь никого, кто бы упоминал о realpath. Не уверен, есть ли какие-либо недостатки в этом подходе.

на macOS, вам нужно установить coreutils, чтобы использовать realpath. Например: brew install coreutils.

0 голосов
/ 14 января 2009

Это должно сработать:

echo `pwd`/`dirname $0`

Это может выглядеть уродливо в зависимости от того, как он был вызван и cwd, но должно привести вас туда, куда вам нужно (или вы можете настроить строку, если вам все равно, как она выглядит).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...