Maneira confiável para um script bash para obter o caminho completo para si mesmo?

Eu tenho um script bash que precisa conhecer seu caminho completo. Estou tentando encontrar uma maneira amplamente compatível de fazer isso sem acabar com caminhos relativos ou de aparência descolada. Eu só preciso apoiar bash, não sh, csh, etc.

O que eu encontrei até agora:

  1. A resposta aceita para ” Obtendo o diretório de origem de um script Bash de dentro ” endereços recebendo o caminho do script via dirname $0 , o que é bom, mas que pode retornar um caminho relativo (como . ), Que é um problema se você quiser para alterar diretórios no script e fazer com que o caminho ainda aponte para o diretório do script. Ainda assim, dirname fará parte do quebra-cabeça.

  2. A resposta aceita para ” Bash script caminho absoluto com OSX ” (OS X específico, mas a resposta funciona independentemente) dá uma function que irá testar para ver se $0 parece relativo e se assim for pré-pendurar $PWD para ele. Mas o resultado ainda pode ter bits relativos (embora no geral seja absoluto) – por exemplo, se o script não estiver no diretório /usr/bin e você estiver no /usr e você digitar bin/../bin/t para executá-lo (sim, isso é complicado), você acaba com /usr/bin/../bin como o caminho do diretório do script. O que funciona , mas …

  3. A solução de readlink nesta página , que se parece com isso:

     # Absolute path to this script. /home/user/bin/foo.sh SCRIPT=$(readlink -f $0) # Absolute path this script is in. /home/user/bin SCRIPTPATH=`dirname $SCRIPT` 

    Mas readlink não é POSIX e, aparentemente, a solução depende do readlink do GNU, onde o BSD não funciona por algum motivo (eu não tenho access a um sistema similar ao BSD para checar).

Então, várias maneiras de fazer isso, mas todas elas têm suas advertências.

Qual seria o melhor caminho? Onde “melhor” significa:

  • Me dá o caminho absoluto.
  • Extrai bits funky mesmo quando invocado de uma maneira complicada (veja o comentário no item 2 acima). (Por exemplo, pelo menos moderadamente canoniza o caminho.)
  • Depende apenas de bash-isms ou coisas que são quase certo que estão nos sabores mais populares dos sistemas * nix (sistemas GNU / Linux, BSD e similares a BSD como OS X, etc.).
  • Evita a chamada de programas externos, se possível (por exemplo, prefere embutidos bash).
  • ( Atualizado , obrigado pelo heads-up, que ) Não tem que resolver links simbólicos (na verdade, eu meio que preferiria deixá-los sozinhos, mas isso não é um requisito).

   

Aqui está o que eu tenho com (editar: além de alguns ajustes fornecidos pelo sfstewman , levigroker , Kyle Strand e Rob Kennedy ), que parece se encheckboxr na maioria dos meus critérios “melhores”:

 SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )" 

Essa linha SCRIPTPATH parece particularmente rotatória, mas nós precisamos dela em vez de SCRIPTPATH=`pwd` para manipular corretamente espaços e links simbólicos.

Note também que situações esotéricas, como a execução de um script que não vem de um arquivo em um sistema de arquivos acessível (o que é perfeitamente possível), não são atendidas (ou em qualquer outra resposta que eu tenha visto). ).

Estou surpreso que o comando realpath não tenha sido mencionado aqui. Meu entendimento é que é amplamente portável / portável.

Sua solução inicial torna-se:

 SCRIPT=`realpath $0` SCRIPTPATH=`dirname $SCRIPT` 

E deixar links simbólicos não resolvidos de acordo com sua preferência:

 SCRIPT=`realpath -s $0` SCRIPTPATH=`dirname $SCRIPT` 

A maneira mais simples que eu encontrei para obter um caminho canônico completo no bash é usar cd e pwd :

 ABSOLUTE_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" 

Usar ${BASH_SOURCE[0]} vez de $0 produz o mesmo comportamento, independentemente de o script ser chamado como ou source

Eu só tive que revisitar este problema hoje e encontrei https://stackoverflow.com/a/246128/1034080 . Elabora uma solução que usei no passado também .

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

Há mais variantes na resposta vinculada, por exemplo, para o caso em que o script em si é um link simbólico.

Obtenha o caminho absoluto do script de shell

Não usa a opção -f no readlink, portanto deve funcionar em bsd / mac-osx

Suporta

  • source ./script (quando chamado pelo operador . dot)
  • Caminho absoluto / caminho / para / script
  • Caminho relativo como ./script
  • /path/dir1/../dir2/dir3/../script
  • Quando chamado do symlink
  • Quando o symlink está nested, por exemplo,) foo->dir1/dir2/bar bar->./../doe doe->script
  • Quando o chamador altera o nome dos scripts

Eu estou procurando por casos de canto onde este código não funciona . Por favor deixe-me saber.

Código

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

Issuse conhecido

O script deve estar no disco em algum lugar , seja em uma rede. Se você tentar executar este script a partir de um PIPE, ele não funcionará

 wget -o /dev/null -O - http://host.domain/dir/script.sh |bash 

Tecnicamente falando, é indefinido.
Praticamente falando, não há maneira sensata de detectar isso. (co-processo não pode acessar env de pai)

Que tal usar:

 SCRIPT_PATH=$(dirname `which $0`) 

which imprime para stdout o caminho completo do executável que teria sido executado quando o argumento passado tinha sido inserido no prompt do shell (que é o que $ 0 contém)

dirname retira o sufixo não-diretório do nome do arquivo

Daí o que você acaba com o caminho completo para o script, não importa se o caminho foi especificado ou não.

Como o realpath não está instalado por padrão no meu sistema Linux, o seguinte funciona para mim:

 SCRIPT="$(readlink --canonicalize-existing "$0")" SCRIPTPATH="$(dirname "$SCRIPT")" 

$SCRIPT conterá o caminho real do arquivo para o script e $SCRIPTPATH o caminho real do diretório que contém o script.

Antes de usar isto leia os comentários desta resposta .

Respondendo esta pergunta muito tarde, mas eu uso:

 SCRIPT=$( readlink -m $( type -p $0 )) # Full path to script BASE_DIR=`dirname ${SCRIPT}` # Directory script is run in NAME=`basename ${SCRIPT}` # Actual name of script even if linked 

Colocamos nosso próprio produto realpath-lib no GitHub para uso comunitário livre e livre.

Plugue sem vergonha, mas com esta biblioteca Bash você pode:

 get_realpath  

Esta function é o núcleo da biblioteca:

 function get_realpath() { if [[ -f "$1" ]] then # file *must* exist if cd "$(echo "${1%/*}")" &>/dev/null then # file *may* not be local # exception is ./file.ext # try 'cd .; cd -;' *works!* local tmppwd="$PWD" cd - &>/dev/null else # file *must* be local local tmppwd="$PWD" fi else # file *cannot* exist return 1 # failure fi # reassemble realpath echo "$tmppwd"/"${1##*/}" return 0 # success } 

Não requer dependencies externas, apenas Bash 4+. Também contém funções para get_dirname , get_filename , get_stemname e validate_path validate_realpath . É grátis, limpo, simples e bem documentado, por isso pode ser usado também para fins de aprendizagem e, sem dúvida, pode ser melhorado. Experimente nas plataformas.

Update: Após algumas revisões e testes, substituímos a function acima por algo que atinge o mesmo resultado (sem usar o dirname, apenas o Bash puro), mas com melhor eficiência:

 function get_realpath() { [[ ! -f "$1" ]] && return 1 # failure : file does not exist. [[ -n "$no_symlinks" ]] && local pwdp='pwd -P' || local pwdp='pwd' # do symlinks. echo "$( cd "$( echo "${1%/*}" )" 2>/dev/null; $pwdp )"/"${1##*/}" # echo result. return 0 # success } 

Isso também inclui uma configuração de ambiente no_symlinks que fornece a capacidade de resolver links simbólicos para o sistema físico. Por padrão, ele mantém os links simbólicos intactos.

Você pode tentar definir a seguinte variável:

 CWD="$(cd -P -- "$(dirname -- "$0")" && pwd -P)" 

ou você pode tentar a seguinte function no bash:

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

Esta function leva 1 argumento. Se o argumento já tiver um caminho absoluto, imprima-o como está, caso contrário, imprima o argumento $PWD variable + filename (sem ./ prefixo).

Relacionado:

  • Caminho absoluto do script Bash com o OSX

  • Obtendo o diretório de origem de um script Bash de dentro

sh compliant maneira:

 SCRIPT_HOME=`dirname $0 | while read a; do cd $a && pwd && break; done` 

simplesmente: BASEDIR=$(readlink -f $0 | xargs dirname)

sem operadores sofisticados

HIH

Considerando esse problema novamente: há uma solução muito popular que é referenciada nesse segmento que tem sua origem aqui :

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

Eu fiquei longe dessa solução por causa do uso de dirname – ela pode apresentar dificuldades em várias plataformas, especialmente se um script precisar ser bloqueado por razões de segurança. Mas como alternativa Bash pura, que tal usar:

 DIR="$( cd "$( echo "${BASH_SOURCE[0]%/*}" )" && pwd )" 

Isso seria uma opção?

Talvez a resposta aceita para a seguinte pergunta possa ser de ajuda.

Como posso obter o comportamento do readlink -f do GNU em um Mac?

Dado que você quer apenas canonicalizar o nome que você obtém da concatenação de $ PWD e $ 0 (assumindo que $ 0 não é absoluto para começar) Apenas use uma série de substituições de regex ao longo da linha de abs_dir=${abs_dir//\/.\//\/} e tal.

Sim, eu sei que parece horrível, mas vai funcionar e é puro bash.

A solução aceita tem o inconveniente (para mim) de não ser “capaz de fonte”:
se você chamá-lo de uma ” source ../../yourScript “, $0 seria ” bash “!

A function a seguir (para bash> = 3.0) me fornece o caminho correto, no entanto, o script pode ser chamado (diretamente ou por meio da source , com um caminho absoluto ou relativo):
(por “caminho certo”, quero dizer o caminho absoluto completo do script sendo chamado , mesmo quando chamado de outro caminho, diretamente ou com ” source “)

 #!/bin/bash echo $0 executed function bashscriptpath() { local _sp=$1 local ascript="$0" local asp="$(dirname $0)" #echo "b1 asp '$asp', b1 ascript '$ascript'" if [[ "$asp" == "." && "$ascript" != "bash" && "$ascript" != "./.bashrc" ]] ; then asp="${BASH_SOURCE[0]%/*}" elif [[ "$asp" == "." && "$ascript" == "./.bashrc" ]] ; then asp=$(pwd) else if [[ "$ascript" == "bash" ]] ; then ascript=${BASH_SOURCE[0]} asp="$(dirname $ascript)" fi #echo "b2 asp '$asp', b2 ascript '$ascript'" if [[ "${ascript#/}" != "$ascript" ]]; then asp=$asp ; elif [[ "${ascript#../}" != "$ascript" ]]; then asp=$(pwd) while [[ "${ascript#../}" != "$ascript" ]]; do asp=${asp%/*} ascript=${ascript#../} done elif [[ "${ascript#*/}" != "$ascript" ]]; then if [[ "$asp" == "." ]] ; then asp=$(pwd) ; else asp="$(pwd)/${asp}"; fi fi fi eval $_sp="'$asp'" } bashscriptpath H export H=${H} 

A chave é detectar o caso ” source ” e usar ${BASH_SOURCE[0]} para recuperar o script atual.

Se usarmos o Bash, acredito que esta é a maneira mais conveniente, pois não requer chamadas para nenhum comando externo:

 THIS_PATH="${BASH_SOURCE[0]}"; THIS_DIR=$(dirname $THIS_PATH) 

Fácil de ler? alternativa. Ignora links simbólicos

 #!/bin/bash currentDir=$( cd $(dirname "$0") pwd ) echo -n "current " pwd echo script $currentDir 

Tente isto:

 cd $(dirname $([ -L $0 ] && readlink -f $0 || echo $0)) 

Apenas para o inferno eu fiz um pouco de hacking em um script que faz as coisas puramente textualmente, puramente no bash. Espero ter apanhado todos os casos extremos. Note que o ${var//pat/repl} que eu mencionei na outra resposta não funciona, já que você não pode fazê-lo replace apenas a correspondência mais curta possível, o que é um problema para replace o /foo/../ como Por exemplo, /*/../ levará tudo antes, não apenas uma única input. E como esses padrões não são realmente regexes, não vejo como isso pode funcionar. Então aqui está a solução bem complicada que eu inventei, aproveite. 😉

By the way, deixe-me saber se você encontrar algum caso de borda não tratada.

 #!/bin/bash canonicalize_path() { local path="$1" OIFS="$IFS" IFS=$'/' read -a parts < <(echo "$path") IFS="$OIFS" local i=${#parts[@]} local j=0 local back=0 local -a rev_canon while (($i > 0)); do ((i--)) case "${parts[$i]}" in ""|.) ;; ..) ((back++));; *) if (($back > 0)); then ((back--)) else rev_canon[j]="${parts[$i]}" ((j++)) fi;; esac done while (($j > 0)); do ((j--)) echo -n "/${rev_canon[$j]}" done echo } canonicalize_path "/.././..////../foo/./bar//foo/bar/.././bar/../foo/bar/./../..//../foo///bar/" 

Eu usei a seguinte abordagem com sucesso por um tempo (não no OSX) e ela só usa o shell embutido e lida com o caso ‘source foobar.sh’ até onde eu vi.

Um problema com o código de exemplo (em conjunto) é que a function usa $ PWD, que pode ou não estar correta no momento da chamada da function. Então, isso precisa ser tratado.

 #!/bin/bash function canonical_path() { # Handle realtive vs absolute path [ ${1:0:1} == '/' ] && x=$1 || x=$PWD/$1 # Change to dirname of x cd ${x%/*} # Combine new pwd with basename of x echo $(pwd -P)/${x##*/} cd $OLDPWD } echo $(canonical_path "${BASH_SOURCE[0]}") type [ type cd type echo type pwd 

Ainda outra maneira de fazer isso:

 shopt -s extglob selfpath=$0 selfdir=${selfpath%%+([!/])} while [[ -L "$selfpath" ]];do selfpath=$(readlink "$selfpath") if [[ ! "$selfpath" =~ ^/ ]];then selfpath=${selfdir}${selfpath} fi selfdir=${selfpath%%+([!/])} done echo $selfpath $selfdir 

Um forro

 `dirname $(realpath $0)` 

Mais simplesmente, isso é o que funciona para mim:

 MY_DIR=`dirname $0` source $MY_DIR/_inc_db.sh