O shell script do Unix descobre em qual diretório o arquivo de script reside?
511
Basicamente, eu preciso executar o script com caminhos relacionados ao local do arquivo de script do shell. Como posso alterar o diretório atual para o mesmo diretório em que o arquivo de script reside?
Isso é realmente uma duplicata? Esta pergunta é sobre um "script shell unix", a outra especificamente sobre o Bash.
precisa
2
@BoltClock: Esta questão foi encerrada incorretamente. A pergunta vinculada é sobre o Bash. Esta pergunta é sobre a programação shell Unix. Observe que as respostas aceitas são bem diferentes!
Dietrich Epp
@ Dietrich Epp: Você está certo. Parece que a escolha do autor da resposta aceita e a adição da tag [bash] (provavelmente em resposta a isso) me levou a marcar a pergunta como uma duplicata em resposta a uma bandeira.
Isso não funciona se você tiver chamado o script por um link simbólico em um diretório diferente. Para fazer esse trabalho, você precisa usá-lo readlinktambém (veja a resposta de todos abaixo)
AndrewR
44
No bash, é mais seguro usá-lo, $BASH_SOURCEem vez de $0, porque $0nem sempre contém o caminho do script que está sendo chamado, como quando o 'sourcing' de um script.
mklement0
2
$BASH_SOURCEé específico do Bash, a questão é sobre o shell script em geral.
Ha-Duong Nguyen 27/05
7
@auraham: CUR_PATH=$(pwd)ou pwdretorne o diretório atual (que não precisa ser o diretório pai dos scripts)!
Andreas Dietrich
5
Eu tentei o método @ mklement0 recomendado, usando $BASH_SOURCE, e ele retorna o que eu precisava. Meu script está sendo chamado de outro script e $0retorna .while $BASH_SOURCEretorna o subdiretório correto (no meu caso scripts).
David Rissato Cruz 03/12/2015
402
A postagem original contém a solução (ignore as respostas, elas não adicionam nada de útil). O trabalho interessante é feito pelo comando unix mencionado readlinkcom a opção -f. Funciona quando o script é chamado por um caminho absoluto e relativo.
Para bash, sh, ksh:
#!/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
Para tcsh, csh:
#!/bin/tcsh# Absolute path to this script, e.g. /home/user/bin/foo.cshset SCRIPT=`readlink -f "$0"`# Absolute path this script is in, thus /home/user/binset SCRIPTPATH=`dirname "$SCRIPT"`
echo $SCRIPTPATH
Para esclarecer o comentário de @ Ergwun: -fnão é suportado no OS X (como no Lion); lá, você pode deixar -fde se contentar com a resolução em mais de um nível de indireção, por exemplo pushd "$(dirname "$(readlink "$BASH_SOURCE" || echo "$BASH_SOURCE")")", ou pode lançar seu próprio script recursivo de acompanhamento de links simbólicos, conforme demonstrado na postagem vinculada.
mklement0
2
Ainda não entendo por que o OP precisaria do caminho absoluto. Comunicando "." deve funcionar bem se você quiser acessar os arquivos relativos ao caminho roteiros e você chamou o script como ./myscript.sh
Stefan Haberl
10
@StefanHaberl eu acho que seria um problema se você executou o script, enquanto o seu diretório de trabalho atual era diferente da localização do script (por exemplo sh /some/other/directory/script.sh), neste caso, .seria o seu pwd, não/some/other/directory
Jon z
51
Um comentário anterior sobre uma resposta dizia isso, mas é fácil perder entre todas as outras respostas.
Ao usar o bash:
echo this file:"$BASH_SOURCE"
echo this dir:"$(dirname "$BASH_SOURCE")"
Somente este trabalha com script no caminho do ambiente, os mais votados não funcionam. obrigado!
julho
Em dirname "$BASH_SOURCE"vez disso, você deve usar para manipular espaços em $ BASH_SOURCE.
Mygod
1
Uma maneira mais explícita de imprimir o diretório seria, de acordo com a ferramenta ShellCheck, ser: "$ (dirname" $ {BASH_SOURCE {0}} ")" porque BASH_SOURCE é uma matriz e, sem o subscrito, o primeiro elemento é escolhido por padrão. .
Adrian M.
1
@AdrianM. , você deseja colchetes, não chaves, para o índice:"$(dirname "${BASH_SOURCE[0]}")"
Hashbrown 12/08/19
Não é bom para me..prints apenas um ponto (ou seja, o diretório atual, presumivelmente)
Esse script deve imprimir o diretório em que você está e, em seguida, o diretório em que ele está. Por exemplo, ao chamá-lo /com o script in /home/mez/, ele gera
//home/mez
Lembre-se, ao atribuir variáveis a partir da saída de um comando, envolva o comando $(e )- ou você não obterá a saída desejada.
Isso não funcionará quando eu chamar o script do diretório atual.
Eric Wang
1
@ EricWang você está sempre no diretório atual.
ctrl-alt-Delor
Para mim, $ current_dir é realmente o caminho do qual estou chamando o script. No entanto, $ script_dir não é o diretório do script, é apenas um ponto.
Michael
31
Se você estiver usando o 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}"
Você pode substituir o pushd/ popdpor cd $(dirname "${0}")e cd -fazê-lo funcionar em outros shells, se eles tiverem um pwd -L.
docwhat
por que você usaria pushd e popd aqui?
Qdeninja
1
Portanto, não preciso armazenar o diretório original em uma variável. É um padrão que eu uso muito em funções e coisas do gênero. Nidifica muito bem, o que é bom.
docwhat
Ele ainda está sendo armazenado na memória - em uma variável - independentemente de uma variável ser referenciada no seu script ou não. Além disso, acredito que o custo da execução de pushd e popd supera em muito a economia de não criar uma variável local Bash em seu script, tanto em ciclos de CPU quanto em legibilidade.
ingyhere
20
Como sugere o Marko:
BASEDIR=$(dirname $0)
echo $BASEDIR
Isso funciona, a menos que você execute o script no mesmo diretório em que o script reside; nesse caso, você obtém o valor '.'
Agora você pode usar a variável current_dir em todo o script para se referir ao diretório de scripts. No entanto, isso ainda pode ter o problema de link simbólico.
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}")" && pwd )"
Uma linha que fornecerá o nome completo do diretório do script, não importa de onde ele esteja sendo chamado.
Para entender como funciona, você pode executar o seguinte script:
#!/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 locatedfidone
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'"
conforme escrito, cd: too many argumentsse espaços no caminho, e retorna $PWD. (uma correção óbvio, mas mostra o quão muitos casos de ponta realmente existem)
michael
1
Voto a favor. Exceto pelo comentário do @michael, que falha com espaços no caminho ... Existe uma correção para isso?
dirname $ 0 só funcionará se o usuário iniciar o script de uma maneira muito específica. Consegui encontrar várias situações em que essa resposta falha e trava o script.
Primeiro de tudo, vamos entender como essa resposta funciona. Ele está obtendo o diretório de scripts fazendo
dirname "$0"
$ 0 representa a primeira parte do comando que chama o script (é basicamente o comando inserido sem os argumentos:
/some/path/./script argument1 argument2
$ 0 = "/ some / caminho /./ script"
dirname basicamente encontra o último / em uma string e o trunca lá. Então, se você fizer:
dirname /usr/bin/sha256sum
você receberá: / usr / bin
Este exemplo funciona bem porque / usr / bin / sha256sum é um caminho formatado corretamente, mas
dirname "/some/path/./script"
não funcionaria bem e daria a você:
BASENAME="/some/path/."#which would crash your script if you try to use it as a path
Digamos que você esteja no mesmo diretório do seu script e o inicie com este comando
./script
$ 0 nesta situação será ./script e dirname $ 0 fornecerá:
.#or BASEDIR=".", again this will crash your script
Usando:
sh script
Sem inserir o caminho completo, também será gerado um BASEDIR = "."
Usando diretórios relativos:
../some/path/./script
Dá um nome de diretório $ 0 de:
../some/path/.
Se você estiver no diretório / some e chamar o script dessa maneira (observe a ausência de / no começo, novamente um caminho relativo):
path/./script.sh
Você obterá esse valor para dirname $ 0:
path/.
e ./path/./script (outra forma do caminho relativo) fornece:
./path/.
As únicas duas situações em que o $ 0 funcionará é se o usuário usar sh ou touch para iniciar um script, pois ambos resultarão em $ 0:
$0=/some/path/script
que lhe dará um caminho que você pode usar com o dirname.
A SOLUÇÃO
Você deve ter em conta e detectar cada uma das situações mencionadas acima e aplicar uma correção para isso, se surgir:
#!/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 scriptif["$debug"= true ];then echo 'BASEDIR=$(dirname "$0") gives: '"$BASEDIR";fiif["$BASEDIR"="."];then BASEDIR="$(pwd)";fi# fix for situation1
_B2=${BASEDIR:$((${#BASEDIR}-2))}; B_=${BASEDIR::1}; B_2=${BASEDIR::2}; B_3=${BASEDIR::3}# <- bash onlyif["$_B2"="/."];then BASEDIR=${BASEDIR::$((${#BASEDIR}-1))};fi#fix for situation2 # <- bash onlyif["$B_"!="/"];then#fix for situation3 #<- bash onlyif["$B_2"="./"];then#covers ./relative_path/(./)scriptif["$(pwd)"!="/"];then BASEDIR="$(pwd)/${BASEDIR:2}";else BASEDIR="/${BASEDIR:2}";fielse#covers relative_path/(./)script and ../relative_path/(./)script, using ../relative_path fails if current path is a symbolic linkif["$(pwd)"!="/"];then BASEDIR="$(pwd)/$BASEDIR";else BASEDIR="/$BASEDIR";fififiif["$debug"= true ];then echo "fixed BASEDIR=$BASEDIR";fi
Essa linha informa onde está o script do shell, não importa se você o executou ou se o originou . Além disso, resolve todos os links simbólicos envolvidos, se for o caso:
Tantas respostas, todas plausíveis, cada uma com prós e contras e objetivos ligeiramente divergentes (que provavelmente devem ser declarados para cada uma). Aqui está outra solução que atende ao objetivo principal de ser clara e trabalhar em todos os sistemas, em todas as partes (sem suposições sobre versões readlinkou pwdopções do bash ) e faz razoavelmente o que você espera que aconteça (por exemplo, resolver links simbólicos é um problema interessante, mas geralmente não é o que você realmente deseja), lida com casos de borda como espaços em caminhos, etc., ignora quaisquer erros e usa um padrão sensato se houver algum problema.
Cada componente é armazenado em uma variável separada que você pode usar individualmente:
# 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)"
Pode parecer feio, dependendo de como foi invocado e do cwd, mas deve levá-lo aonde você precisa ir (ou pode ajustar a string se você se importar com a aparência).
Respostas:
No Bash, você deve obter o que precisa assim:
fonte
readlink
também (veja a resposta de todos abaixo)$BASH_SOURCE
em vez de$0
, porque$0
nem sempre contém o caminho do script que está sendo chamado, como quando o 'sourcing' de um script.$BASH_SOURCE
é específico do Bash, a questão é sobre o shell script em geral.CUR_PATH=$(pwd)
oupwd
retorne o diretório atual (que não precisa ser o diretório pai dos scripts)!$BASH_SOURCE
, e ele retorna o que eu precisava. Meu script está sendo chamado de outro script e$0
retorna.
while$BASH_SOURCE
retorna o subdiretório correto (no meu casoscripts
).A postagem original contém a solução (ignore as respostas, elas não adicionam nada de útil). O trabalho interessante é feito pelo comando unix mencionado
readlink
com a opção-f
. Funciona quando o script é chamado por um caminho absoluto e relativo.Para bash, sh, ksh:
Para tcsh, csh:
Consulte também: https://stackoverflow.com/a/246128/59087
fonte
readlink
. É por isso que eu recomendei usar pushd / popd (embutidos para o bash).-f
opção dereadlink
fazer algo diferente no OS X (Lion) e possivelmente no BSD. stackoverflow.com/questions/1055671/…-f
não é suportado no OS X (como no Lion); lá, você pode deixar-f
de se contentar com a resolução em mais de um nível de indireção, por exemplopushd "$(dirname "$(readlink "$BASH_SOURCE" || echo "$BASH_SOURCE")")"
, ou pode lançar seu próprio script recursivo de acompanhamento de links simbólicos, conforme demonstrado na postagem vinculada.sh /some/other/directory/script.sh)
, neste caso,.
seria o seu pwd, não/some/other/directory
Um comentário anterior sobre uma resposta dizia isso, mas é fácil perder entre todas as outras respostas.
Ao usar o bash:
Manual de referência do bash, 5.2 variáveis do bash
fonte
dirname "$BASH_SOURCE"
vez disso, você deve usar para manipular espaços em $ BASH_SOURCE."$(dirname "${BASH_SOURCE[0]}")"
Supondo que você esteja usando o bash
Esse script deve imprimir o diretório em que você está e, em seguida, o diretório em que ele está. Por exemplo, ao chamá-lo
/
com o script in/home/mez/
, ele geraLembre-se, ao atribuir variáveis a partir da saída de um comando, envolva o comando
$(
e)
- ou você não obterá a saída desejada.fonte
Se você estiver usando o bash ....
fonte
pushd
/popd
porcd $(dirname "${0}")
ecd -
fazê-lo funcionar em outros shells, se eles tiverem umpwd -L
.Como sugere o Marko:
Isso funciona, a menos que você execute o script no mesmo diretório em que o script reside; nesse caso, você obtém o valor '.'
Para contornar esse problema, use:
Agora você pode usar a variável current_dir em todo o script para se referir ao diretório de scripts. No entanto, isso ainda pode ter o problema de link simbólico.
fonte
A melhor resposta para esta pergunta foi respondida aqui:
Obtendo o diretório de origem de um script Bash de dentro
E isso é:
Uma linha que fornecerá o nome completo do diretório do script, não importa de onde ele esteja sendo chamado.
Para entender como funciona, você pode executar o seguinte script:
fonte
fonte
Vamos torná-lo um onixiner POSIX:
Testado em muitas conchas compatíveis com Bourne, incluindo as BSD.
Tanto quanto sei, sou o autor e o coloco em domínio público. Para obter mais informações, consulte: https://www.jasan.tk/posts/2017-05-11-posix_shell_dirname_replacement/
fonte
cd: too many arguments
se espaços no caminho, e retorna$PWD
. (uma correção óbvio, mas mostra o quão muitos casos de ponta realmente existem)fonte
Se você deseja obter o diretório de scripts real (independentemente de estar invocando o script usando um link simbólico ou diretamente), tente:
Isso funciona no linux e no macOS. Não vi ninguém aqui mencionar
realpath
. Não tenho certeza se há alguma desvantagem nessa abordagem.no macOS, você precisa instalar
coreutils
para usarrealpath
. Por exemplo:brew install coreutils
.fonte
INTRODUÇÃO
Esta resposta corrige a resposta muito quebrada, mas chocante, mais votada deste tópico (escrita por TheMarko):
POR QUE USAR dirname "$ 0" NÃO É FUNCIONAL?
dirname $ 0 só funcionará se o usuário iniciar o script de uma maneira muito específica. Consegui encontrar várias situações em que essa resposta falha e trava o script.
Primeiro de tudo, vamos entender como essa resposta funciona. Ele está obtendo o diretório de scripts fazendo
$ 0 representa a primeira parte do comando que chama o script (é basicamente o comando inserido sem os argumentos:
$ 0 = "/ some / caminho /./ script"
dirname basicamente encontra o último / em uma string e o trunca lá. Então, se você fizer:
você receberá: / usr / bin
Este exemplo funciona bem porque / usr / bin / sha256sum é um caminho formatado corretamente, mas
não funcionaria bem e daria a você:
Digamos que você esteja no mesmo diretório do seu script e o inicie com este comando
$ 0 nesta situação será ./script e dirname $ 0 fornecerá:
Usando:
Sem inserir o caminho completo, também será gerado um BASEDIR = "."
Usando diretórios relativos:
Dá um nome de diretório $ 0 de:
Se você estiver no diretório / some e chamar o script dessa maneira (observe a ausência de / no começo, novamente um caminho relativo):
Você obterá esse valor para dirname $ 0:
e ./path/./script (outra forma do caminho relativo) fornece:
As únicas duas situações em que o $ 0 funcionará é se o usuário usar sh ou touch para iniciar um script, pois ambos resultarão em $ 0:
que lhe dará um caminho que você pode usar com o dirname.
A SOLUÇÃO
Você deve ter em conta e detectar cada uma das situações mencionadas acima e aplicar uma correção para isso, se surgir:
fonte
Essa linha informa onde está o script do shell, não importa se você o executou ou se o originou . Além disso, resolve todos os links simbólicos envolvidos, se for o caso:
A propósito, suponho que você esteja usando / bin / bash .
fonte
Tantas respostas, todas plausíveis, cada uma com prós e contras e objetivos ligeiramente divergentes (que provavelmente devem ser declarados para cada uma). Aqui está outra solução que atende ao objetivo principal de ser clara e trabalhar em todos os sistemas, em todas as partes (sem suposições sobre versões
readlink
oupwd
opções do bash ) e faz razoavelmente o que você espera que aconteça (por exemplo, resolver links simbólicos é um problema interessante, mas geralmente não é o que você realmente deseja), lida com casos de borda como espaços em caminhos, etc., ignora quaisquer erros e usa um padrão sensato se houver algum problema.Cada componente é armazenado em uma variável separada que você pode usar individualmente:
fonte
Inspirado pela resposta de blueyed
fonte
Isso deve fazer o truque:
Pode parecer feio, dependendo de como foi invocado e do cwd, mas deve levá-lo aonde você precisa ir (ou pode ajustar a string se você se importar com a aparência).
fonte
`pwd`/`dirname $0`
mas ainda pode falhar nos links simbólicos