59 Вопрос: Получить исходный каталог скрипта Bash из самого скрипта

вопрос создан в Fri, Feb 1, 2019 12:00 AM

Как получить путь к каталогу, в котором находится сценарий Bash находится внутри этого скрипта?

Например, допустим, я хочу использовать скрипт Bash в качестве средства запуска для другого приложения. Я хочу изменить рабочий каталог на каталог, в котором находится скрипт Bash, чтобы я мог работать с файлами в этом каталоге, например так:

 
$ ./application
    
4435
  1. Ни одно из текущих решений не работает, если в конце имени каталога есть какие-либо символы новой строки - они будут удалены при подстановке команды. Чтобы обойти это, вы можете добавить не подстановочный символ внутри подстановки команд - DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd && echo x)" - и удалить его без подстановки команд - DIR="${DIR%x}".
    2012-09-24 12: 15: 09Z
  2. @ jpmc26 Существуют две очень распространенные ситуации: аварии и саботаж. Сценарий не должен выходить из строя непредсказуемым образом только потому, что кто-то где-то сделал mkdir $'\n'.
    2013-03-28 08: 14: 48Z
  3. любой, кто разрешает людям саботировать свою систему таким образом, не должен оставлять на bash возможность обнаруживать такие проблемы ... тем более нанимать людей, способных сделать такого рода ошибка. За 25 лет использования bash я никогда не видел, чтобы подобные вещи происходили где-то ... вот почему у нас есть такие вещи, как perl и такие практики, как проверка на зараженность (я, вероятно, буду рад за то, что сказал:)
    2015-02-05 00: 12: 36Z
  4. @ l0b0 Учтите, что вам нужна такая же защита на dirname, и что каталог может начинаться с - (например, --help). DIR=$(reldir=$(dirname -- "$0"; echo x); reldir=${reldir%?x}; cd -- "$reldir" && pwd && echo x); DIR=${DIR%?x}. Возможно, это излишне?
    2015-04-28 17: 46: 47Z
  5. Я настоятельно рекомендую прочитать этот FAQ по Bash о предмете.
    2016-01-30 02: 22: 03Z
  6. 30 ответов                              30                         
     
    #!/bin/bash
    
    DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
    

    - это полезная однострочная строка, которая даст вам полное имя каталога скрипта независимо от того, откуда он вызывается.

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

     
    #!/bin/bash
    
    SOURCE="${BASH_SOURCE[0]}"
    while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
      DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
      SOURCE="$(readlink "$SOURCE")"
      [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
    done
    DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
    

    Этот последний будет работать с любой комбинацией псевдонимов, source, bash -c, символических ссылок и т. д.

    Осторожно: если вы запустили cd в другой каталог перед запуском этого фрагмента, результат может быть неверным!

    Кроме того, следите за $CDPATH полученными ссылками , и побочные эффекты вывода stderr, если пользователь умно переопределил cd для перенаправления вывода на stderr (включая escape-последовательности, например, при вызове update_terminal_cwd >&2 на Mac). Добавление >/dev/null 2>&1 в конце вашей команды cd позаботится об обеих возможностях.

    Чтобы понять, как это работает, попробуйте запустить эту более подробную форму:

     
    #!/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" )" >/dev/null 2>&1 && pwd )"
    if [ "$DIR" != "$RDIR" ]; then
      echo "DIR '$RDIR' resolves to '$DIR'"
    fi
    echo "DIR is '$DIR'"
    

    И он напечатает что-то вроде:

     
    SOURCE './scriptdir.sh' is a relative symlink to 'sym2/scriptdir.sh' (relative to '.')
    SOURCE is './sym2/scriptdir.sh'
    DIR './sym2' resolves to '/home/ubuntu/dotfiles/fo fo/real/real1/real2'
    DIR is '/home/ubuntu/dotfiles/fo fo/real/real1/real2'
    
        
    5898
    2018-12-24 07: 25: 41Z
    1. Вы можете объединить этот подход с ответом пользователя25866, чтобы найти решение, которое работает с source <script> и bash <script>: DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)".
      2011-10-19 15: 54: 43Z
    2. Иногда cd печатает что-то в STDOUT! Например, если ваш $CDPATH имеет .. Чтобы покрыть этот случай, используйте DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )"
      2013-02-03 02: 33: 52Z
    3. Подождите, так что же будет последней командой?
      2014-06-05 14: 19: 19Z
    4. Этот принятый ответ не подходит, он не работает с символическими ссылками и слишком сложен. dirname $(readlink -f $0) - правильная команда. См. gist.github.com/tvlooy/cbfbdb111a4ebad8b93e для тестового случая
      2015-06-09 19: 32: 10Z
    5. @ tvlooy IMO ваш ответ не совсем так, как есть, потому что он терпит неудачу, когда в пути есть пробел. В отличие от символа новой строки, это не маловероятно или даже необычно. dirname "$(readlink -f "$0")" не добавляет сложности и справедливо более надежен для минимального количества проблем.
      2015-10-28 23: 38: 56Z

    Используйте dirname "$0":

     
    #!/bin/bash
    echo "The script you are running has basename `basename "$0"`, dirname `dirname "$0"`"
    echo "The present working directory is `pwd`"
    

    использование только pwd не будет работать, если вы не запускаете сценарий из каталога, в котором он содержится.

     
    [matt@server1 ~]$ pwd
    /home/matt
    [matt@server1 ~]$ ./test2.sh
    The script you are running has basename test2.sh, dirname .
    The present working directory is /home/matt
    [matt@server1 ~]$ cd /tmp
    [matt@server1 tmp]$ ~/test2.sh
    The script you are running has basename test2.sh, dirname /home/matt
    The present working directory is /tmp
    
        
    772
    2018-03-29 22: 51: 06Z
    1. Для переносимости за пределы bash, $0 не всегда может быть достаточно. Возможно, вам придется заменить «type -p $0», чтобы эта работа работала, если команда была найдена в пути.
      2008-10-23 20: 15: 01Z
    2. @ Darron: вы можете использовать type -p, только если скрипт исполняемый. Это также может открыть тонкую дыру, если скрипт выполняется с использованием bash test2.sh, и есть другой скрипт с таким же именем, исполняемый где-то еще.
      2010-02-05 12: 18: 04Z
    3. @ Darron: но так как вопрос помечен bash, а в строке хеш-бэнга явно упоминается /bin/bash, я бы сказал, что довольно безопасно зависеть от bashisms.
      2010-06-11 12: 56: 58Z
    4. + 1, но проблема с использованием dirname $0 состоит в том, что, если каталог является текущим каталогом, вы получите .. Это нормально, если вы не собираетесь менять каталоги в сценарии и ожидать использования пути, который вы получили от dirname $0, как если бы он был абсолютным. Чтобы получить абсолютный путь: pushd `dirname $0` > /dev/null, SCRIPTPATH=`pwd`, popd > /dev/null: pastie.org/1489386 (но , конечно, есть лучший способ расширить этот путь?)
      2011-01-23 10: 30: 43Z
    5. @ T.J. Crowder Я не уверен, что dirname $0 - это проблема, если вы назначаете ее переменной, а затем используете ее для запуска скрипта, подобного $dir/script.sh; Я предполагаю, что это вариант использования для такого типа вещей в 90% случаев. ./script.sh будет работать нормально.
      2011-01-24 12: 55: 08Z

    Команда dirname является самой простой, просто анализируя путь до имени файла из переменной $0 (имя скрипта):

     
    dirname "$0"
    

    Но, как указал matt b , возвращаемый путь зависит от того, как вызывается скрипт. pwd не выполняет эту работу, потому что она говорит только о том, что является текущим каталогом, а не в каком каталоге находится скрипт. Кроме того, если выполняется символическая ссылка на скрипт, вы получите (возможно, относительный) путь где находится ссылка, а не фактический скрипт.

    Некоторые другие упоминали команду readlink , но в простейшем случае вы можете использовать:

     
    dirname "$(readlink -f "$0")"
    

    readlink разрешит путь сценария к абсолютному пути от корня файловой системы. Таким образом, любые пути, содержащие одинарные или двойные точки, тильды и /или символические ссылки, будут преобразованы в полный путь.

    Вот скрипт, демонстрирующий каждый из них, whatdir.sh:

     
    #!/bin/bash
    echo "pwd: `pwd`"
    echo "\$0: $0"
    echo "basename: `basename $0`"
    echo "dirname: `dirname $0`"
    echo "dirname/readlink: $(dirname $(readlink -f $0))"
    

    Запуск этого скрипта в моей домашней директории с использованием относительного пути:

     
    >>>$ ./whatdir.sh 
    pwd: /Users/phatblat
    $0: ./whatdir.sh
    basename: whatdir.sh
    dirname: .
    dirname/readlink: /Users/phatblat
    

    Опять же, но с использованием полного пути к сценарию:

     
    >>>$ /Users/phatblat/whatdir.sh 
    pwd: /Users/phatblat
    $0: /Users/phatblat/whatdir.sh
    basename: whatdir.sh
    dirname: /Users/phatblat
    dirname/readlink: /Users/phatblat
    

    Теперь смена каталогов:

     
    >>>$ cd /tmp
    >>>$ ~/whatdir.sh 
    pwd: /tmp
    $0: /Users/phatblat/whatdir.sh
    basename: whatdir.sh
    dirname: /Users/phatblat
    dirname/readlink: /Users/phatblat
    

    И, наконец, использование символической ссылки для выполнения скрипта:

     
    >>>$ ln -s ~/whatdir.sh whatdirlink.sh
    >>>$ ./whatdirlink.sh 
    pwd: /tmp
    $0: ./whatdirlink.sh
    basename: whatdirlink.sh
    dirname: .
    dirname/readlink: /Users/phatblat
    
        
    424
    2017-01-04 16: 33: 10Z
    1. readlink не будет доступен на некоторых платформах при установке по умолчанию. Старайтесь избегать его использования, если можете
      2012-01-11 09: 14: 04Z
    2. будьте осторожны, чтобы все заключить в кавычки, чтобы избежать проблем с пробелами: export SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
      2013-09-17 19: 40: 27Z
    3. В OSX Yosemite 10.10.1 -f не распознается как опция для readlink. Использование stat -f вместо этого делает работу. Благодаря
      2014-11-26 09: 29: 32Z
    4. В OSX имеется greadlink, что по сути является readlink, который нам всем знаком. Вот независимая от платформы версия: dir=`greadlink -f ${BASH_SOURCE[0]} || readlink -f ${BASH_SOURCE[0]}`
      2016-01-14 20: 16: 28Z
    5. Хороший вызов, @robert. К вашему сведению, greadlink может быть легко установлен через домашний кофе: brew install coreutils
      2016-01-15 21: 27: 26Z
     
    pushd . > /dev/null
    SCRIPT_PATH="${BASH_SOURCE[0]}"
    if ([ -h "${SCRIPT_PATH}" ]); then
      while([ -h "${SCRIPT_PATH}" ]); do cd `dirname "$SCRIPT_PATH"`; 
      SCRIPT_PATH=`readlink "${SCRIPT_PATH}"`; done
    fi
    cd `dirname ${SCRIPT_PATH}` > /dev/null
    SCRIPT_PATH=`pwd`;
    popd  > /dev/null
    

    Работает для всех версий, включая

    • при вызове через мягкую ссылку множественной глубины,
    • когда файл это
    • когда скрипт вызывается командой "source" или оператором . (точка).
    • когда arg $0 изменен из вызывающей стороны.
    •  "./script"
    •  "/full/path/to/script"
    • "/some/path/../../another/path/script"
    •  "./some/folder/script"

    В качестве альтернативы, если сам скрипт bash является относительной символической ссылкой , вы хотите перейти по нему и вернуть полный путь связанного сценария:

     
    pushd . > /dev/null
    SCRIPT_PATH="${BASH_SOURCE[0]}";
    if ([ -h "${SCRIPT_PATH}" ]) then
      while([ -h "${SCRIPT_PATH}" ]) do cd `dirname "$SCRIPT_PATH"`; SCRIPT_PATH=`readlink "${SCRIPT_PATH}"`; done
    fi
    cd `dirname ${SCRIPT_PATH}` > /dev/null
    SCRIPT_PATH=`pwd`;
    popd  > /dev/null
    

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

    Этот комментарий и код Copyleft, выбираемая лицензия под GPL2.0 или более поздней версии или CC-SA 3.0 (CreativeCommons Share Alike) или более поздней. (c) 2008. Все права защищены. Никаких гарантий. Вас предупредили.
    http://www.gnu.org/licenses/gpl -2.0.txt
    http://creativecommons.org/licenses/by -sa /3.0 /
    18eedfe1c99df68dc94d4a94712a71aaa8e1e9e36cacf421b9463dd2bbaa02906d0d6656 р>     

    175
    2018-05-17 11:21:43Z
    1. Отлично! Можно сделать короче, заменив «pushd [...] popd /dev /null» на SCRIPT_PATH = readlink -f $(dirname "${VIRTUAL_ENV}");
      2009-11-29 11: 34: 04Z
    2. И вместо использования pushd ...; не лучше ли использовать $(cd dirname "${SCRIPT_PATH}" & pwd)? Но в любом случае отличный сценарий!
      2010-08-18 10: 16: 00Z
    3. Разве if не является избыточным? while тестирует то же самое ...
      2011-08-05 13: 28: 49Z
    4. Для сценария cd опасно выходить из его текущего каталога в надежде на то, что cd снова вернется назад: сценарий может не иметь разрешения на смену каталога обратно на каталог это было актуально, когда он был вызван. (То же самое касается pushd /popd)
      2012-11-06 01: 15: 21Z
    5. readlink -f зависит от GNU. В BSD readlink такой опции нет.
      2014-06-03 16: 48: 15Z

    Краткий ответ:

     
    `dirname $0`
    

    или ( желательно ):

     
    $(dirname "$0")
    
        
    102
    2017-04-19 06: 59: 25Z
    1. Это не сработает, если вы создадите сценарий. "source my /script.sh"
      2014-02-05 07: 34: 32Z
    2. тогда ничего не будет
      2016-03-03 10: 14: 27Z
    3. Я все время использую это в своих скриптах bash, которые автоматизируют вещи и часто вызывают другие скрипты в том же каталоге. Я бы никогда не использовал source на них, а cd $(dirname $0) легко запомнить.
      2017-01-03 16: 28: 27Z
    4. работает для /bin /sh
      2017-05-29 06: 25: 50Z
    5. @ vidstige: ${BASH_SOURCE[0]} вместо $0 будет работать с source my/script.sh
      2017-09-27 06: 48: 00Z

    Вы можете использовать $BASH_SOURCE

     
    #!/bin/bash
    
    scriptdir=`dirname "$BASH_SOURCE"`
    

    Обратите внимание, что вам нужно использовать #! /bin /bash, а не #! /bin /sh, поскольку это расширение bash

        
    98
    2014-06-17 10: 12: 50Z
    1. Когда я делаю ./foo/script, то $(dirname $BASH_SOURCE) равен ./foo.
      2010-10-25 17: 06: 20Z
    2. также работает с источником /. оператор!
      2011-08-11 20: 53: 38Z

    Это должно сделать это:

     
    DIR=$(dirname "$(readlink -f "$0")")
    

    Работает с символическими ссылками и пробелами в пути. См. Справочные страницы для dirname и readlink .

    Изменить.

    Судя по комментариям, он не работает с Mac OS. Я понятия не имею, почему это так. Есть предложения?

        
    62
    2017-07-29 22: 04: 57Z
    1. с вашим решением, при вызове сценария, например ./script.sh, отображается . вместо полного пути к каталогу
      2016-06-14 18: 27: 08Z
    2. В MacOS нет опции -f для readlink. Используйте stat вместо этого. Но, тем не менее, он показывает ., если вы находитесь в этой директории.
      2016-11-21 09: 55: 33Z
    3. Это лучшее решение в Ubuntu 16.04
      2017-03-19 15: 50: 31Z
    4. Но не в Mac Bash!
      2017-07-28 20: 06: 34Z
    5. Вам необходимо установить coreutils из Homebrew и использовать greadlink, чтобы получить опцию -f в MacOS, поскольку это * BSD под прикрытием, а не Linux.
      2019-04-22 15: 59: 00Z

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

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

    Хотя просто

     
    cd `dirname $0`
    

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

        
    55
    2016-04-01 15: 23: 56Z
    1. Вы можете сделать все это в одну строку следующим образом: DIRECTORY = $(cd dirname $0 & &pwd)
      2008-10-29 08: 38: 07Z
    2. Это не работает, если скрипт использует другой скрипт, и вы хотите знать имя последнего.
      2014-03-28 13: 10: 27Z

    Я не думаю, что это так просто, как это сделали другие. pwd не работает, так как текущий каталог не обязательно является каталогом со скриптом. $0 также не всегда имеет информацию. Рассмотрим следующие три способа вызова сценария.

     
    ./script
    
    /usr/bin/script
    
    script
    

    В первом и третьем способах $0 не имеет полной информации о пути. Во втором и третьем pwd не работают. Единственный способ получить каталог третьим способом - это запустить путь и найти файл с правильным соответствием. В основном код должен был бы переделывать то, что делает ОС.

    Один из способов сделать то, что вы просите, - это просто жестко закодировать данные в каталоге /usr /share и сослаться на них по полному пути. В любом случае данные не должны находиться в /usr /bin dir, так что это, вероятно, то, что нужно сделать.

        
    33
    2011-12-02 14: 56: 57Z
    1. Если вы намерены опровергнуть его комментарий, ДОКАЗАТЬ, что скрипт МОЖЕТ получить доступ к тому месту, где он хранится, с примером кода.
      2015-11-18 18: 54: 12Z
     
    SCRIPT_DIR=$( cd ${0%/*} && pwd -P )
    
        
    32
    2013-05-30 11: 28: 27Z
    1. Это намного короче, чем выбранный ответ. И, кажется, работает так же хорошо. Это заслуживает 1000 голосов, чтобы люди не упустили это из виду.
      2013-09-19 03: 07: 26Z
    2. Как подробно объясняется во многих предыдущих ответах, ни $0, ни pwd не гарантированно содержат правильную информацию в зависимости от того, как вызывается скрипт.
      2013-09-23 16: 51: 44Z

    Получает текущий рабочий каталог в Mac OS X 10.6.6:

     
    DIR=$(cd "$(dirname "$0")"; pwd)
    
        
    28
    2012-12-03 13: 29: 20Z
     
    $(dirname "$(readlink -f "$BASH_SOURCE")")
    
        
    27
    2018-05-13 04: 36: 30Z

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

    Вот скрипт, который легко запомнить:

     
    DIR=$(dirname "${BASH_SOURCE[0]}")  # get the directory name
    DIR=$(realpath "${DIR}")    # resolve its full path if need be
    
        
    27
    2018-11-07 04: 30: 59Z
    1. realpath происходит из GNU coreutils , но кто-нибудь знает, насколько он распространен среди других дисков?
      2019-03-15 09: 09: 53Z
    2. Или, что более странно, в одной строке: DIR=$(realpath "$(dirname "${BASH_SOURCE[0]}")")
      2019-04-04 11: 55: 34Z

    Это специфично для Linux, но вы можете использовать:

     
    SELF=$(readlink /proc/$$/fd/255)
    
        
    26
    2013-12-22 00: 07: 53Z
    1. Это также относится к bash, но, возможно, поведение bash изменилось? /proc/fd/$$/255, кажется, указывает на tty, а не на каталог. Например, в моей текущей оболочке входа в систему дескрипторы файлов 0, 1, 2 и 255 ссылаются на /dev/pts/4. В любом случае, руководство по bash не упоминает fd 255, поэтому, вероятно, неразумно зависеть от этого поведения. \
      2015-03-29 00: 17: 33Z
    2. Интерактивная оболочка! = скрипт. В любом случае, realpath ${BASH_SOURCE[0]}; может показаться лучшим вариантом.
      2015-04-06 12: 52: 06Z

    Вот POSIX-совместимая однострочная строка:

     
    SCRIPT_PATH=`dirname "$0"`; SCRIPT_PATH=`eval "cd \"$SCRIPT_PATH\" && pwd"`
    
    # test
    echo $SCRIPT_PATH
    
        
    21
    2013-04-15 07: 28: 08Z
    1. Я успешно справился с этим, запустив скрипт самостоятельно или используя sudo, но не при вызове source ./script.sh
      2013-04-17 21: 57: 40Z
    2. И происходит сбой, когда cd настроен для печати нового имени пути.
      2013-11-04 13: 45: 25Z

    Я попробовал каждый из них, и ни один из них не сработал. Один был очень близко, но имел крошечную ошибку, которая сильно его сломала; они забыли обернуть путь в кавычки.

    Также многие полагают, что вы запускаете скрипт из оболочки, поэтому забудьте, что при открытии нового скрипта он по умолчанию устанавливается в вашем доме.

    Попробуйте этот каталог по размеру:

    /var /No one /Мысли /О Пространствах /В каталоге /Имя /И вот ваш file.text

    Это правильно, независимо от того, как и где вы его запускаете.

     
    #!/bin/bash
    echo "pwd: `pwd`"
    echo "\$0: $0"
    echo "basename: `basename "$0"`"
    echo "dirname: `dirname "$0"`"
    

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

     
    cd "`dirname "$0"`"
    

    Надеюсь, это поможет

        
    16
    2011-02-06 02: 04: 03Z
    1. Не работает, если скрипт получен из другого скрипта.
      2014-03-28 13: 12: 03Z

    Вот простой, правильный способ:

     
    actual_path=$(readlink -f "${BASH_SOURCE[0]}")
    script_dir=$(dirname "$actual_path")
    

    Пояснение:

    • ${BASH_SOURCE[0]} - полный путь к сценарию. Значение этого будетЯ буду прав, даже когда сценарий находится в процессе поиска, например, source <(echo 'echo $0') печатает bash , а при его замене на ${BASH_SOURCE[0]} печатается полный путь скрипта. (Конечно, это предполагает, что вы в порядке, принимая зависимость от Bash.)

    • readlink -f - рекурсивно разрешает любые символические ссылки в указанном пути. Это расширение GNU, которое недоступно (например, в системах BSD). Если вы работаете на Mac, вы можете использовать Homebrew для установки GNU coreutils и заменить его greadlink -f .

    • И, конечно же, dirname получает родительский каталог пути.

    16
    2016-02-20 07: 53: 23Z
    1. SCRIPT_DIR=$(dirname $(readlink -f "${BASH_SOURCE[0]}"))
      2018-10-31 18: 19: 14Z
    2. greadlink -f, к сожалению, не работает эффективно при source сценарии на Mac: (
      2019-04-30 23: 50: 08Z

    Я бы использовал что-то вроде этого:

     
    # retrieve the full pathname of the called script
    scriptPath=$(which $0)
    
    # check whether the path is a link or not
    if [ -L $scriptPath ]; then
    
        # it is a link then retrieve the target path and get the directory name
        sourceDir=$(dirname $(readlink -f $scriptPath))
    
    else
    
        # otherwise just get the directory name of the script path
        sourceDir=$(dirname $scriptPath)
    
    fi
    
        
    15
    2012-11-21 03: 57: 55Z
    1. Это настоящий! Работает с простой sh тоже! Проблема с простыми решениями на основе dirname "$0": если скрипт находится в $PATH и вызывается без пути, он даст неверный результат.
      2014-11-18 10: 25: 02Z
    2. @ Notinlist Не так. Если скрипт найден через PATH, $0 будет содержать абсолютное имя файла. Если скрипт вызывается с относительным или абсолютным именем файла, содержащим /, $0 будет содержать его.
      2016-02-03 22: 08: 15Z

    Небольшой пересмотр решения e-sat и 3bcdnlklvc04a указан в их ответ

     
    SCRIPT_DIR=''
    pushd "$(dirname "$(readlink -f "$BASH_SOURCE")")" > /dev/null && {
        SCRIPT_DIR="$PWD"
        popd > /dev/null
    }    
    

    Это должно работать во всех перечисленных случаях.

    РЕДАКТИРОВАТЬ: предотвратить popd после неудачного pushd, благодаря konsolebox

        
    14
    2017-05-23 10: 31: 38Z
    1. Это прекрасно работает, чтобы получить "реальное" имя_каталога, а не просто имя символической ссылки. Спасибо!
      2010-06-23 20: 32: 50Z
    2. Лучше SCRIPT_DIR=''; pushd "$(dirname "$(readlink -f "$BASH_SOURCE")")" > /dev/null && { SCRIPT_DIR=$PWD; popd > /dev/null; }
      2014-07-03 04: 15: 43Z
    3. @ konsolebox, от чего вы пытаетесь защищаться? Я вообще фанат встраивания логических условных выражений, но какую конкретно ошибку вы увидели в pushd? Я бы предпочел найти способ обработать его напрямую, а не возвращать пустой SCRIPT_DIR.
      2015-01-19 20: 03: 31Z
    4. @ Fuwjax Естественная практика - избегать popd в случаях (даже в редких случаях), когда pushd дает сбой. И в случае неудачи pushd, как вы думаете, что должно быть значением SCRIPT_DIR? Действие может варьироваться в зависимости от того, что может показаться логичным или от того, что может предпочесть один пользователь, но, безусловно, выполнение popd является неправильным.
      2015-01-20 19: 21: 28Z
     
    #!/bin/sh
    PRG="$0"
    
    # need this for relative symlinks
    while [ -h "$PRG" ] ; do
       PRG=`readlink "$PRG"`
    done
    
    scriptdir=`dirname "$PRG"`
    
        
    13
    2008-09-13 01: 28: 47Z

    $_ стоит упомянуть в качестве альтернативы $0 , Если вы запускаете скрипт из bash, принятый ответ может быть сокращен до:

     
    DIR="$( dirname "$_" )"
    

    Обратите внимание, что это должно быть первым утверждением в вашем скрипте.

        
    11
    2011-09-16 19: 05: 51Z
    1. Он ломается, если вы source или . сценарий. В этих ситуациях $_ будет содержать последний параметр последней команды, которую вы выполнили до .. $BASH_SOURCE работает каждый раз.
      2014-01-31 14: 55: 13Z

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

    • Абсолютные или относительные пути
    • Программные ссылки на файлы и каталоги
    • Вызывается как script, bash script, bash -c script, source script или . script
    • Пробелы, вкладки, переводы строк, юникод и т. д. в каталогах и /или именах файлов
    • Имена файлов, начинающиеся с дефиса

    Если вы работаете в Linux, кажется, что использование дескриптора proc является лучшим решением для поиска полностью разрешенного источника запущенного в данный момент сценария (в интерактивном сеансе ссылка указывает на соответствующий /dev/pts/X): р>  

    resolved="$(readlink /proc/$$/fd/255 && echo X)" && resolved="${resolved%$'\nX'}"
    

    Это немного уродливо, но исправление компактно и легко для понимания. Мы не используем только примитивы bash, но я согласен с этим, потому что readlink значительно упрощает задачу. echo X добавляет X в конец строки переменной, так что любые конечные пробелы в имени файла не съедаются, а подстановка параметров ${VAR%X} в конце строки избавляет от X. Поскольку readlink добавляет новую строку (которая обычно используется в подстановке команд, если бы не наш предыдущий трюк), мы также должны избавиться от этого. Этого легче всего достичь, используя схему цитирования $'', которая позволяет нам использовать escape-последовательности, такие как \n, для представления новых строк (это также то, как вы можете легко создавать каталоги и файлы с обманчивым именем).

    Вышеприведенное должно охватывать ваши потребности в поиске текущего запущенного скрипта в Linux, но если в вашем распоряжении нет файловой системы proc или вы пытаетесь найти полностью разрешенный путь какого-либо другого файла, тогда может быть, вы найдете следующий код полезным. Это всего лишь небольшая модификация вышеупомянутой однострочной. Если вы играете со странными каталогами /именами файлов, проверка выходных данных с ls и readlink является информативной, так как ls выведет «упрощенные» пути, заменив ? такими вещами, как перевод строки.

     
    absolute_path=$(readlink -e -- "${BASH_SOURCE[0]}" && echo x) && absolute_path=${absolute_path%?x}
    dir=$(dirname -- "$absolute_path" && echo x) && dir=${dir%?x}
    file=$(basename -- "$absolute_path" && echo x) && file=${file%?x}
    
    ls -l -- "$dir/$file"
    printf '$absolute_path: "%s"\n' "$absolute_path"
    
        
    11
    2015-07-25 19: 14: 09Z
    1. Я получаю /dev/pts/30 с bash на Ubuntu 14.10 Desktop.
      2015-08-15 11: 29: 23Z
    2. @ DanDascalescu Использование однострочника? Или полный фрагмент кода внизу? И вы выдумывали какие-нибудь хитрые пути?
      2015-08-19 06: 34: 53Z
    3. Одна строка плюс другая строка для echo $resolved, я сохранил ее как d, chmod +x d, ./d.
      2015-08-20 05: 55: 38Z
    4. @ DanDascalescu Первая строка в вашем скрипте должна быть #!/bin/bash
      2015-08-23 05: 07: 50Z
    5. 2015-08-23 05: 17: 31Z

    Попробуйте использовать:

     
    real=$(realpath $(dirname $0))
    
    10
    2012-02-06 11: 20: 08Z
    1. Все, что я хочу знать, - почему этот путь не годится? Это казалось не плохим и правильным для меня. Может ли кто-нибудь объяснить, почему за него проголосовали?
      2012-08-28 15: 16: 52Z
    2. realpath не является стандартной утилитой.
      2013-05-13 12: 06: 13Z
    3. В Linux realpath - это стандартная утилита (часть пакета GNU coreutils), но она не является встроенной в bash (то есть функцией, предоставляемой самой bash). ). Если вы работаете в Linux, этот метод, вероятно, будет работать, хотя я бы заменил $0 на ${BASH_SOURCE[0]}, чтобы этот метод работал везде, в том числе в функции.
      2014-07-18 15: 53: 46Z
    4. Порядок операций в этом ответе неправильный. Вам необходимо сначала разрешить символическую ссылку, затем сделать dirname, потому что последняя часть $0 может быть символической ссылкой, которая указывает на файл, который не находится в том же каталоге, что и символическая ссылка сам. Решение, описанное в этом ответе, просто получает путь к каталогу, в котором хранится символическая ссылка, а не каталог цели. Кроме того, в этом решении отсутствует цитирование. Это не будет работать, если путь содержит специальные символы.
      2015-04-13 21: 43: 23Z
    5. Это решение "просто получает путь к каталогу, в котором хранится символическая ссылка", и это то, что я на самом деле хочу. PS: цитата еще должна быть добавлена.
      2019-03-11 10: 19: 28Z

    Для систем, имеющих GNU coreutils readlink (например, linux):

     
    $(readlink -f "$(dirname "$0")")
    

    Нет необходимости использовать BASH_SOURCE, когда $0 содержит имя файла сценария.

        
    10
    2018-05-13 04: 34: 37Z
    1. , если сценарий не был получен. или 'source', в этом случае это все равно будет тот сценарий, из которого он получен, или, если из командной строки, '-bash' (tty login) или 'bash' (вызывается через 'bash -l') или '/bin /bash '(вызывается как интерактивная оболочка без авторизации)
      2015-02-05 00: 07: 46Z
    2. Я добавил вторую пару кавычек около dirname вызова. Необходим, если путь к каталогу содержит пробелы.
      2018-05-13 04: 35: 07Z

    Итак ... я верю, что у меня есть этот. Поздно на вечеринку, но я думаю, что некоторые оценят, что они здесь сталкиваются с этой темой. Комментарии должны объяснить.

     
    #!/bin/sh # dash bash ksh # !zsh (issues). G. Nixon, 12/2013. Public domain.
    
    ## 'linkread' or 'fullpath' or (you choose) is a little tool to recursively
    ## dereference symbolic links (ala 'readlink') until the originating file
    ## is found. This is effectively the same function provided in stdlib.h as
    ## 'realpath' and on the command line in GNU 'readlink -f'.
    
    ## Neither of these tools, however, are particularly accessible on the many
    ## systems that do not have the GNU implementation of readlink, nor ship
    ## with a system compiler (not to mention the requisite knowledge of C).
    
    ## This script is written with portability and (to the extent possible, speed)
    ## in mind, hence the use of printf for echo and case statements where they
    ## can be substituded for test, though I've had to scale back a bit on that.
    
    ## It is (to the best of my knowledge) written in standard POSIX shell, and
    ## has been tested with bash-as-bin-sh, dash, and ksh93. zsh seems to have
    ## issues with it, though I'm not sure why; so probably best to avoid for now.
    
    ## Particularly useful (in fact, the reason I wrote this) is the fact that
    ## it can be used within a shell script to find the path of the script itself.
    ## (I am sure the shell knows this already; but most likely for the sake of
    ## security it is not made readily available. The implementation of "$0"
    ## specificies that the $0 must be the location of **last** symbolic link in
    ## a chain, or wherever it resides in the path.) This can be used for some
    ## ...interesting things, like self-duplicating and self-modifiying scripts.
    
    ## Currently supported are three errors: whether the file specified exists
    ## (ala ENOENT), whether its target exists/is accessible; and the special
    ## case of when a sybolic link references itself "foo -> foo": a common error
    ## for beginners, since 'ln' does not produce an error if the order of link
    ## and target are reversed on the command line. (See POSIX signal ELOOP.)
    
    ## It would probably be rather simple to write to use this as a basis for
    ## a pure shell implementation of the 'symlinks' util included with Linux.
    
    ## As an aside, the amount of code below **completely** belies the amount
    ## effort it took to get this right -- but I guess that's coding for you.
    
    ##===-------------------------------------------------------------------===##
    
    for argv; do :; done # Last parameter on command line, for options parsing.
    
    ## Error messages. Use functions so that we can sub in when the error occurs.
    
    recurses(){ printf "Self-referential:\n\t$argv ->\n\t$argv\n" ;}
    dangling(){ printf "Broken symlink:\n\t$argv ->\n\t"$(readlink "$argv")"\n" ;}
    errnoent(){ printf "No such file: "$@"\n" ;} # Borrow a horrible signal name.
    
    # Probably best not to install as 'pathfull', if you can avoid it.
    
    pathfull(){ cd "$(dirname "$@")"; link="$(readlink "$(basename "$@")")"
    
    ## 'test and 'ls' report different status for bad symlinks, so we use this.
    
     if [ ! -e "$@" ]; then if $(ls -d "$@" 2>/dev/null) 2>/dev/null;  then
        errnoent 1>&2; exit 1; elif [ ! -e "$@" -a "$link" = "$@" ];   then
        recurses 1>&2; exit 1; elif [ ! -e "$@" ] && [ ! -z "$link" ]; then
        dangling 1>&2; exit 1; fi
     fi
    
    ## Not a link, but there might be one in the path, so 'cd' and 'pwd'.
    
     if [ -z "$link" ]; then if [ "$(dirname "$@" | cut -c1)" = '/' ]; then
       printf "$@\n"; exit 0; else printf "$(pwd)/$(basename "$@")\n"; fi; exit 0
     fi
    
    ## Walk the symlinks back to the origin. Calls itself recursivly as needed.
    
     while [ "$link" ]; do
       cd "$(dirname "$link")"; newlink="$(readlink "$(basename "$link")")"
       case "$newlink" in
        "$link") dangling 1>&2 && exit 1                                       ;;
             '') printf "$(pwd)/$(basename "$link")\n"; exit 0                 ;;
              *) link="$newlink" && pathfull "$link"                           ;;
       esac
     done
     printf "$(pwd)/$(basename "$newlink")\n"
    }
    
    ## Demo. Install somewhere deep in the filesystem, then symlink somewhere 
    ## else, symlink again (maybe with a different name) elsewhere, and link
    ## back into the directory you started in (or something.) The absolute path
    ## of the script will always be reported in the usage, along with "$0".
    
    if [ -z "$argv" ]; then scriptname="$(pathfull "$0")"
    
    # Yay ANSI l33t codes! Fancy.
     printf "\n\033[3mfrom/as: \033[4m$0\033[0m\n\n\033[1mUSAGE:\033[0m   "
     printf "\033[4m$scriptname\033[24m [ link | file | dir ]\n\n         "
     printf "Recursive readlink for the authoritative file, symlink after "
     printf "symlink.\n\n\n         \033[4m$scriptname\033[24m\n\n        "
     printf " From within an invocation of a script, locate the script's "
     printf "own file\n         (no matter where it has been linked or "
     printf "from where it is being called).\n\n"
    
    else pathfull "$@"
    fi
    
        
    8
    2013-12-21 17: 47: 37Z

    Попробуйте следующее кросс-совместимое решение:

     
    CWD="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
    

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

    Примечание. В Bash рекомендуется использовать ${BASH_SOURCE[0]} вместо $0, в противном случае путь может оборваться при поиске файла (source/.).

    В качестве альтернативы вы можете попробовать следующую функцию в bash:

     
    realpath () {
      [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"
    }
    

    Эта функция принимает 1 аргумент. Если аргумент уже имеет абсолютный путь, выведите его как есть, в противном случае выведите переменную $PWD + аргумент имени файла (без префикса ./).

    по теме:

    8
    2019-06-23 21: 18: 54Z
    1. Пожалуйста, объясните больше о функции realpath.
      2015-03-27 16: 54: 26Z
    2. @ Функция Chris realpath принимает 1 аргумент. Если аргумент уже имеет абсолютный путь, выведите его как есть, в противном случае выведите $PWD + имя файла (без префикса ./).
      2015-03-27 17: 35: 01Z
    3. Ваше кросс-совместимое решение не работает, когда скрипт имеет символическую ссылку.
      2015-09-08 20: 59: 09Z

    Хм, если в пути указано базовое имя & dirname просто не собираются его резать и идти по пути трудно (что если родитель не экспортировал PATH!). Тем не менее, оболочка должна иметь открытый дескриптор своего сценария, и в bash, ручка # 255.

     
    SELF=`readlink /proc/$$/fd/255`
    

    у меня работает.

        
    7
    2011-07-27 20: 17: 03Z
    1. Я получаю /dev/pts/30 с bash на Ubuntu 14.10 Desktop вместо реального каталога, из которого я запускаю скрипт.
      2015-08-15 11: 30: 30Z

    Подводя итог множеству ответов:

     
        Script: "/tmp/src dir/test.sh"
        Calling folder: "/tmp/src dir/other"
    

    Используемые команды

     
        echo Script-Dir : `dirname "$(realpath $0)"`
        echo Script-Dir : $( cd ${0%/*} && pwd -P )
        echo Script-Dir : $(dirname "$(readlink -f "$0")")
        echo
        echo Script-Name : `basename "$(realpath $0)"`
        echo Script-Name : `basename $0`
        echo
        echo Script-Dir-Relative : `dirname "$BASH_SOURCE"`
        echo Script-Dir-Relative : `dirname $0`
        echo
        echo Calling-Dir : `pwd`
    

    Вывод:

     
         Script-Dir : /tmp/src dir
         Script-Dir : /tmp/src dir
         Script-Dir : /tmp/src dir
    
         Script-Name : test.sh
         Script-Name : test.sh
    
         Script-Dir-Relative : ..
         Script-Dir-Relative : ..
    
         Calling-Dir : /tmp/src dir/other
    

    См https://pastebin.com/J8KjxrPF

        
    7
    2018-03-09 12: 56: 45Z

    Это работает в bash-3.2:

     
    path="$( dirname "$( which "$0" )" )"
    

    Вот пример его использования:

    Скажем, у вас есть каталог ~ /bin , который находится в вашем $PATH . У вас есть скрипт A внутри этого каталога. Это исходный сценарий ~ /bin /lib /B . Вы знаете, где включенный скрипт относительно исходного (подкаталог lib ), а не где он находится относительно текущего каталога пользователя.

    Это решается следующим образом (внутри A ):

     
    source "$( dirname "$( which "$0" )" )/lib/B"
    

    Неважно, где находится пользователь или как он вызывает скрипт, это всегда будет работать.

        
    6
    2010-09-03 06: 44: 30Z
    1. Вопрос о which очень спорен. type, hash и другие встроенные команды делают то же самое лучше в bash. which более переносим, ​​хотя на самом деле это не то же самое, что which, используемый в других оболочках, таких как tcsh, в котором он встроен.
      2014-01-13 22: 30: 04Z
    2. "Всегда"? Не за что. which, будучи внешним инструментом, у вас нет оснований полагать, что он ведет себя идентично родительской оболочке.
      2014-06-09 03: 42: 39Z

    Ничто из этого не работало для скрипта bash, запущенного Finder в OS X - в итоге я использовал:

     
    SCRIPT_LOC="`ps -p $$ | sed /PID/d | sed s:.*/Network/:/Network/: |
    sed s:.*/Volumes/:/Volumes/:`"
    

    Не красиво, но оно выполняет свою работу.

        
    5
    2013-02-14 17: 23: 28Z
источник размещен Вот