Como obter o diretório de origem de um script Bash de dentro do próprio script

4953

Como obtenho o caminho do diretório em que um script Bash está localizado, dentro desse script?

Quero usar um script Bash como iniciador para outro aplicativo. Desejo alterar o diretório de trabalho para o local em que o script Bash está localizado, para que eu possa operar nos arquivos desse diretório, da seguinte maneira:

$ ./application
Jiaaro
fonte
69
Nenhuma das soluções atuais funciona se houver novas linhas no final do nome do diretório - elas serão removidas pela substituição do comando. Para contornar isso, você pode acrescentar um caractere que não seja de nova linha dentro da substituição de comando - DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd && echo x)"- e removê-lo sem uma substituição de comando - DIR="${DIR%x}".
L0b0 24/09/12
80
@ jpmc26 Existem duas situações muito comuns: acidentes e sabotagem. Um script não deve falhar de maneiras imprevisíveis apenas porque alguém, em algum lugar, fez um mkdir $'\n'.
L0b0 28/03
24
qualquer um que permita que as pessoas sabotem seu sistema dessa maneira não deve deixar de detectar tais problemas ... muito menos contratar pessoas capazes de cometer esse tipo de erro. Eu nunca, nos 25 anos de uso do bash, vi esse tipo de coisa acontecer em qualquer lugar ... é por isso que temos coisas como perl e práticas como verificação de contaminação (eu provavelmente ficarei inflamado por dizer isso :)
osirisgothra 5/02
3
@ l0b0 Considere que você precisaria da mesma proteção dirnamee que o diretório poderia começar com um -(por exemplo --help). DIR=$(reldir=$(dirname -- "$0"; echo x); reldir=${reldir%?x}; cd -- "$reldir" && pwd && echo x); DIR=${DIR%?x}. Talvez isso seja um exagero?
Score_Under
56
Sugiro enfaticamente ler este FAQ do Bash sobre o assunto.
Rany Albeg Wein

Respostas:

6572
#!/bin/bash

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"

é um one-liner útil que fornecerá o nome completo do diretório do script, não importa de onde ele esteja sendo chamado.

Ele funcionará desde que o último componente do caminho usado para encontrar o script não seja um link simbólico (os links do diretório estão OK). Se você também quiser resolver quaisquer links para o próprio script, precisará de uma solução de várias linhas:

#!/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 )"

Este último irá trabalhar com qualquer combinação de aliases, source, bash -c, links simbólicos, etc.

Cuidado: se você estiver cdem um diretório diferente antes de executar este trecho, o resultado pode estar incorreto!

Além disso, preste atenção às $CDPATHdicas e aos efeitos colaterais do stderr se o usuário substituiu o cd de maneira inteligente para redirecionar a saída para o stderr (incluindo sequências de escape, como ao chamar o update_terminal_cwd >&2Mac). Adicionar >/dev/null 2>&1no final do seu cdcomando cuidará das duas possibilidades.

Para entender como funciona, tente executar este formulário mais detalhado:

#!/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'"

E imprimirá algo como:

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'
Dave Dopson
fonte
27
Você pode fundir essa abordagem com a resposta por user25866 para chegar a uma solução que funciona com source <script>e bash <script>: DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)".
Dan Molding
21
Às vezes cdimprime algo para STDOUT! Por exemplo, se você $CDPATHtiver .. Para cobrir esse caso, useDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )"
user716468
182
Esta resposta aceita não está ok, não funciona com links simbólicos e é excessivamente complexa. dirname $(readlink -f $0)é o comando certo. Veja gist.github.com/tvlooy/cbfbdb111a4ebad8b93e para uma testcase
tvlooy
167
@tvlooy IMO, sua resposta também não é exatamente como está, porque falha quando há um espaço no caminho. Ao contrário de um caractere de nova linha, isso não é improvável nem incomum. dirname "$(readlink -f "$0")"não adiciona complexidade e é uma medida justa mais robusta para a quantidade mínima de problemas.
Adrian Günter
11
@tvlooy, seu comentário não é compatível com o macOS (ou provavelmente com o BSD em geral), enquanto a resposta aceita é. readlink -f $0readlink: illegal option -- f.
Alexander Ljungberg
875

Use dirname "$0":

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

usar pwdsozinho não funcionará se você não estiver executando o script a partir do diretório em que está contido.

[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
mate b
fonte
25
Para portabilidade além do bash, $ 0 nem sempre pode ser suficiente. Pode ser necessário substituir "type -p $ 0" para fazer isso funcionar se o comando foi encontrado no caminho.
Darron
10
@ Darron: você só pode usar type -pse o script for executável. Isso também pode abrir um buraco sutil se o script for executado usando bash test2.she houver outro script com o mesmo nome executável em outro lugar.
precisa saber é o seguinte
90
@ Darron: mas como a pergunta está marcada bashe a linha hash-bang menciona explicitamente, /bin/basheu diria que é bem seguro depender de basismos.
Joachim Sauer
34
+1, mas o problema com o uso dirname $0é que, se o diretório for o atual, você receberá .. Tudo bem, a menos que você altere os diretórios no script e espere usar o caminho que obteve dirname $0como se fosse absoluto. Para obter o caminho absoluto: pushd `dirname $0` > /dev/null, SCRIPTPATH=`pwd`, popd > /dev/null: pastie.org/1489386 (Mas certamente há uma maneira melhor para expandir esse caminho?)
TJ Crowder
9
@TJ Crowder Não tenho certeza se dirname $0é um problema se você atribuí-lo a uma variável e depois usá-lo para iniciar um script como $dir/script.sh; Eu imagino que este é o caso de uso para esse tipo de coisa 90% do tempo. ./script.shfuncionaria bem.
Matt b
515

O dirnamecomando é o mais básico, basta analisar o caminho até o nome do arquivo da $0variável (nome do script):

dirname "$0"

Mas, como matt b apontou, o caminho retornado é diferente dependendo de como o script é chamado. pwdnão faz o trabalho porque isso apenas informa qual é o diretório atual, não o diretório em que o script reside. Além disso, se um link simbólico para um script for executado, você obterá um caminho (provavelmente relativo) para onde o link reside, não o script real.

Alguns outros mencionaram o readlinkcomando, mas, na sua forma mais simples, você pode usar:

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

readlinkresolverá o caminho do script para um caminho absoluto a partir da raiz do sistema de arquivos. Portanto, quaisquer caminhos que contenham pontos simples ou duplos, tildes e / ou links simbólicos serão resolvidos para um caminho completo.

Aqui está um script demonstrando cada um destes 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))"

Executando este script no meu diretório home, usando um caminho relativo:

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

Novamente, mas usando o caminho completo para o script:

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

Agora alterando diretórios:

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

E, finalmente, usando um link simbólico para executar o script:

>>>$ ln -s ~/whatdir.sh whatdirlink.sh
>>>$ ./whatdirlink.sh 
pwd: /tmp
$0: ./whatdirlink.sh
basename: whatdirlink.sh
dirname: .
dirname/readlink: /Users/phatblat
phatblat
fonte
13
readlinknão estará disponível em algumas plataformas na instalação padrão. Tente evitar usá-lo se você puder
TL
43
tenha cuidado para citar tudo para evitar problemas de espaço em branco:export SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
Catskul 17/13
13
No OSX, o Yosemite 10.10.1 -fnão é reconhecido como uma opção para readlink. Usando, em stat -fvez disso, faz o trabalho. Graças
cucu8
11
No OSX, existe greadlinkbasicamente o readlinkque todos conhecemos. Aqui está uma versão independente da plataforma:dir=`greadlink -f ${BASH_SOURCE[0]} || readlink -f ${BASH_SOURCE[0]}`
robert
6
Boa chamada, @robert. FYI, greadlinkpode ser facilmente instalado através do homebrew:brew install coreutils
phatblat
184
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

Funciona para todas as versões, incluindo

  • quando chamado via link direto de profundidade múltipla,
  • quando o arquivo
  • quando script chamado pelo comando " source" aka .operador (ponto).
  • quando arg $0é modificado a partir do chamador.
  • "./script"
  • "/full/path/to/script"
  • "/some/path/../../another/path/script"
  • "./some/folder/script"

Como alternativa, se o próprio script bash for um link simbólico relativo, você deseja segui-lo e retornar o caminho completo do script vinculado ao:

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é dado no caminho completo, não importa como é chamado.
Apenas certifique-se de localizá-lo no início do script.

Este comentário e código Copyleft, licença selecionável sob a GPL2.0 ou posterior ou CC-SA 3.0 (CreativeCommons Share Alike) ou posterior. (c) 2008. Todos os direitos reservados. Nenhuma garantia de qualquer tipo. Você foi avisado.
http://www.gnu.org/licenses/gpl-2.0.txt
http://creativecommons.org/licenses/by-sa/3.0/
18eedfe1c99df68dc94d4a94712a71aaa8e1e9e36cacf421b9463dd2bbaa02906d0d6656

user25866
fonte
4
Agradável! Pode ser abreviado substituindo "pushd [...] popd / dev / null" por SCRIPT_PATH = readlink -f $(dirname "${VIRTUAL_ENV}");
E-satis
5
E em vez de usar pushd ...; não seria melhor usar $ (cd dirname "${SCRIPT_PATH}"&& pwd)? Mas de qualquer maneira ótimo roteiro!
ovanes 18/08/10
6
É perigoso para um script cdsair do diretório atual na esperança de cdretornar novamente mais tarde: O script pode não ter permissão para alterar o diretório novamente para o diretório que estava atual quando foi invocado. (O mesmo vale para pushd / popd)
Adrian Pronk
7
readlink -fé específico do GNU. O BSD readlinknão tem essa opção.
Kara Brightwell
3
O que há com todos os subshells desnecessários? ([ ... ])é menos eficiente do que [ ... ]e não há vantagem no isolamento oferecido em troca do desempenho atingido aqui.
Charles Duffy
110

Resposta curta:

`dirname $0`

ou ( preferencialmente ):

$(dirname "$0")
Fabien
fonte
17
Não funcionará se você originar o script. "source my / script.sh"
Arunprasad Rajkumar
Eu uso isso o tempo todo em meus scripts bash que automatizam coisas e frequentemente invocam outros scripts no mesmo diretório. Eu nunca usaria sourceisso e cd $(dirname $0)é fácil de lembrar.
kqw
16
@vidstige: em ${BASH_SOURCE[0]}vez de $0trabalhar com #source my/script.sh
Timothy Jones
@ TimothyJones que falhará 100% do tempo se originado de qualquer outro shell que não seja o bash. ${BASH_SOURCE[0]}não é satisfatório. ${BASH_SOURCE:-0}é muito melhor.
Mathieu CAROFF
106

Você pode usar $BASH_SOURCE:

#!/bin/bash

scriptdir=`dirname "$BASH_SOURCE"`

Observe que você precisa usar, #!/bin/bashe não #!/bin/shporque é uma extensão do Bash.

Mr Shark
fonte
14
Quando eu faço ./foo/script, então $(dirname $BASH_SOURCE)é ./foo.
Até
1
@ Até este caso, podemos usar o realpathcomando para obter o caminho completo de ./foo/script. Então, dirname $(realpath ./foo/script) vai dar o caminho do script.
Purushothaman poovai
73

Isso deve servir:

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

Isso funciona com links simbólicos e espaços no caminho.

Veja as páginas de manual para dirnamee readlink.

Na faixa de comentários, parece não funcionar com o Mac OS. Não faço ideia do porquê disso. Alguma sugestão?

Simon Rigét
fonte
6
com a sua solução, invocando o script como ./script.shmostra .em vez do caminho do diretório completo
de Bruno Negrão Zica
5
Não há opção -f para readlink no MacOS. Use em statvez disso. Mas ainda assim, mostra .se você está no diretório 'this'.
Denis The Menace
2
Você precisa instalar a coreutilspartir do Homebrew e usar greadlinkpara obter a -fopção no MacOS, pois é * BSD oculto e não Linux.
precisa saber é o seguinte
Você deve adicionar aspas duplas ao redor do lado direito:DIR="$(dirname "$(readlink -f "$0")")"
hagello 17/03
60

pwdpode ser usado para encontrar o diretório de trabalho atual e dirnameo diretório de um arquivo específico (o comando que foi executado é $0, portanto, dirname $0deve fornecer o diretório do script atual).

No entanto, dirnamefornece com precisão a parte do diretório do nome do arquivo, que provavelmente será relativa ao diretório de trabalho atual. Se o seu script precisar alterar o diretório por algum motivo, a saída de dirnameficará sem sentido.

Sugiro o seguinte:

#!/bin/bash

reldir=`dirname $0`
cd $reldir
directory=`pwd`

echo "Directory is $directory"

Dessa forma, você obtém um diretório absoluto, e não relativo.

Como o script será executado em uma instância do bash separada, não há necessidade de restaurar o diretório de trabalho posteriormente, mas se você quiser voltar ao script por algum motivo, poderá atribuir facilmente o valor de pwda uma variável antes de alterar diretório, para uso futuro.

Embora apenas

cd `dirname $0`

resolve o cenário específico da pergunta, acho que ter o caminho absoluto para mais útil em geral.

SpoonMeiser
fonte
9
Você pode fazer tudo isso em uma linha como esta: DIRECTORY = $ (cd dirname $0&& pwd)
dogbane
Isso não funciona se o script origina outro script e você deseja saber o nome do último.
reinierpost
52

Estou cansado de vir a esta página repetidamente para copiar e colar o verso na resposta aceita. O problema é que não é fácil entender e lembrar.

Aqui está um script fácil de lembrar:

DIR="$(dirname "${BASH_SOURCE[0]}")"  # get the directory name
DIR="$(realpath "${DIR}")"    # resolve its full path if need be
Thamme Gowda
fonte
2
Ou, mais obscuramente, em uma linha: DIR=$(realpath "$(dirname "${BASH_SOURCE[0]}")")
agc
Por que essa não é a resposta aceita? Existe alguma diferença usando a realpathresolução "manualmente" com um loop de readlink? Até a readlinkpágina do manual dizNote realpath(1) is the preferred command to use for canonicalization functionality.
User9123 25/03
1
E, a propósito, não devemos aplicar realpathantes dirname, não depois? Se o arquivo de script em si for um link simbólico ... Daria algo parecido DIR="$(dirname "$(realpath "${BASH_SOURCE[0]}")")". Na verdade, muito perto da resposta proposta por Simon.
User9123
@ User9123 Acho que o que aceita é tentar ser compatível com todos os shell / distros populares. Além disso, dependendo do que você está tentando fazer, na maioria dos casos as pessoas desejam obter o diretório em que o link simbólico estava localizado, em vez do diretório da fonte real.
Wang
37

Eu não acho que isso seja tão fácil quanto os outros fizeram parecer. pwdnão funciona, pois o diretório atual não é necessariamente o diretório com o script. $0nem sempre tem as informações. Considere as três maneiras a seguir para invocar um script:

./script

/usr/bin/script

script

Na primeira e na terceira maneira $0, não há informações completas do caminho. No segundo e terceiro, pwdnão funciona. A única maneira de obter o diretório da terceira maneira seria percorrer o caminho e encontrar o arquivo com a correspondência correta. Basicamente, o código teria que refazer o que o sistema operacional faz.

Uma maneira de fazer o que você está pedindo é apenas codificar os dados no /usr/sharediretório e referenciá-los por seu caminho completo. Os dados não devem estar no /usr/bindiretório de qualquer maneira, portanto é provavelmente o que deve ser feito.

Jim
fonte
9
Se você pretende refutar o comentário dele, PROVE que um script PODE acessar onde está armazenado com um exemplo de código.
Richard Duerr
34
SCRIPT_DIR=$( cd ${0%/*} && pwd -P )
PM
fonte
Isso é muito mais curto que a resposta escolhida. E parece funcionar tão bem. Isso merece 1000 votos apenas para que as pessoas não o ignorem.
Patrick
2
Como muitas das respostas anteriores explicam em detalhes, $0nem pwdé garantido que você tem as informações corretas, dependendo de como o script é chamado.
IMSoP 23/09
34
$(dirname "$(readlink -f "$BASH_SOURCE")")
teste11
fonte
Prefiro, $BASH_SOURCEmais $0, porque é explícito mesmo para os leitores que não conhecem bem o bash. $(dirname -- "$(readlink -f -- "$BASH_SOURCE")")
blobmaster 8/01
32

Isso obtém o diretório de trabalho atual no Mac OS X 10.6.6:

DIR=$(cd "$(dirname "$0")"; pwd)
Pubguy
fonte
27

Isso é específico do Linux, mas você pode usar:

SELF=$(readlink /proc/$$/fd/255)
Steve Baker
fonte
1
Também é específico do bash, mas talvez o comportamento do bash tenha mudado? /proc/fd/$$/255parece apontar para o tty, não para um diretório. Por exemplo, no meu shell de logon atual, os descritores de arquivo 0, 1, 2 e 255 se referem a todos /dev/pts/4. Em qualquer caso, o manual do bash não menciona fd 255, por isso é provavelmente imprudente depender este comportamento \.
Keith Thompson
2
Shell interativo! = Script. Enfim realpath ${BASH_SOURCE[0]};, parece ser o melhor caminho a percorrer.
9788 Steve
23

Aqui está uma linha única compatível com POSIX:

SCRIPT_PATH=`dirname "$0"`; SCRIPT_PATH=`eval "cd \"$SCRIPT_PATH\" && pwd"`

# test
echo $SCRIPT_PATH
lamawithonel
fonte
4
Eu tive sucesso com isso ao executar um script por si só ou usando sudo, mas não ao chamar ./script.sh fonte
Michael R
E falha quando cdestá configurado para imprimir o novo nome do caminho.
Aaron Digulla
18

Eu tentei tudo isso e nenhum funcionou. Um estava muito próximo, mas tinha um pequeno inseto que o quebrou muito; eles esqueceram de colocar o caminho entre aspas.

Muitas pessoas também assumem que você está executando o script a partir de um shell, para que se esqueçam que quando você abre um novo script, o padrão é o seu lar.

Tente este diretório para obter o tamanho:

/var/No one/Thought/About Spaces Being/In a Directory/Name/And Here's your file.text

Isso acerta, independentemente de como ou onde você o executa:

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

Portanto, para torná-lo realmente útil, veja como mudar para o diretório do script em execução:

cd "`dirname "$0"`"
Mike Bethany
fonte
4
Não funciona se o script estiver sendo originado de outro script.
reinierpost
Isso não funciona se a última parte de $ 0 for um link simbólico apontando para uma entrada de outro diretório ( ln -s ../bin64/foo /usr/bin/foo).
hagello 17/03
17

Aqui está a maneira simples e correta:

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

Explicação:

  • ${BASH_SOURCE[0]}- o caminho completo para o script. O valor disso estará correto mesmo quando o script estiver sendo originado, por exemplo, source <(echo 'echo $0')imprime bash , ao substituí-lo por ${BASH_SOURCE[0]}imprime o caminho completo do script. (Obviamente, isso pressupõe que você não tenha problemas com o Bash.)

  • readlink -f- Resolve recursivamente todos os links simbólicos no caminho especificado. Esta é uma extensão GNU e não está disponível em (por exemplo) sistemas BSD. Se você estiver executando um Mac, poderá usar o Homebrew para instalar o GNU coreutilse substituí-lo greadlink -f.

  • E, claro, dirnameobtém o diretório pai do caminho.

James Ko
fonte
1
greadlink -finfelizmente, não funciona de forma eficaz quando sourceing o script no Mac :(
Gabe Kopley
17

A maneira mais curta e elegante de fazer isso é:

#!/bin/bash
DIRECTORY=$(cd `dirname $0` && pwd)
echo $DIRECTORY

Isso funcionaria em todas as plataformas e é super limpo.

Mais detalhes podem ser encontrados em "Em qual diretório está esse script bash? ".

Atul
fonte
ótima solução limpa, mas isso não funcionará se o arquivo tiver um link simbólico.
ruuter 26/09/19
16

Eu usaria algo como isto:

# 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
Nicolas
fonte
Este é o verdadeiro! Funciona com simples shtambém! Problema com dirname "$0"soluções simples : se o script estiver no $PATHe for chamado sem caminho, eles fornecerão resultados incorretos.
Notinlist
@ Notinlist Não é assim. Se o script for encontrado através do PATH, $0conterá o nome do arquivo absoluto. Se o script for chamado com um nome de arquivo relativo ou absoluto contendo a /, $0ele conterá isso.
Neil Mayhew
Não funcionará para um script de origem.
Amit Naidu
16

Esta é uma pequena revisão da solução e-satis e 3bcdnlklvc04a, apontada em sua resposta :

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

Isso ainda deve funcionar em todos os casos listados.

Isso evitará popdapós uma falha pushd, graças ao konsolebox.

Fuwjax
fonte
Isso funciona perfeitamente para obter o dirname "real", em vez de apenas o nome de um link simbólico. Obrigado!
Jay Taylor
1
MelhorSCRIPT_DIR=''; pushd "$(dirname "$(readlink -f "$BASH_SOURCE")")" > /dev/null && { SCRIPT_DIR=$PWD; popd > /dev/null; }
konsolebox
@konsolebox, contra o que você está tentando se defender? Geralmente sou fã de condicionais lógicos embutidos, mas qual foi o erro específico que você estava vendo no pushd? Eu prefiro encontrar uma maneira de lidar com isso diretamente, em vez de retornar um SCRIPT_DIR vazio.
Fuwjax 19/01
@Fuwjax Prática natural para evitar fazer popdcasos (mesmo quando raros) em que pushdfalha. E, caso pushdfalhe, qual você acha que deve ser o valor SCRIPT_DIR? A ação pode variar de acordo com o que pode parecer lógico ou o que um usuário pode preferir, mas certamente, fazer algo popderrado.
precisa saber é o seguinte
Todos esses pushd popdperigos poderiam ser evitados simplesmente descartando-os e usando cd+ pwdincluído em uma substituição de comando. SCRIPT_DIR=$(...)
Amit Naidu
16

Para sistemas com coreutils GNU readlink(por exemplo, linux):

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

Não há necessidade de usar BASH_SOURCEquando $0contém o nome do arquivo do script.

user1338062
fonte
3
a menos que o script tenha sido originado. ou 'source'; nesse caso, ainda será o script que o originou ou, se a partir da linha de comando, '-bash' (tty login) ou 'bash' (chamado via 'bash -l') ou '/ bin / bash'(invocada como uma concha não-login interativo)
osirisgothra
Eu adicionei o segundo par de aspas na dirnamechamada. Necessário se o caminho do diretório contiver espaços.
User1338062
14
#!/bin/sh
PRG="$0"

# need this for relative symlinks
while [ -h "$PRG" ] ; do
   PRG=`readlink "$PRG"`
done

scriptdir=`dirname "$PRG"`
Monkeyboy
fonte
Não testei em diferentes sistemas. Mas esta solução é a que funciona imediatamente, pelo menos no Ubuntu, para mim!
Natus Drew
$0não funcionará para um script de origem
Amit Naidu
13

$_vale a pena mencionar como uma alternativa para $0. Se você estiver executando um script do Bash, a resposta aceita poderá ser reduzida para:

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

Observe que essa deve ser a primeira declaração do seu script.

hurrymaplelad
fonte
4
Ele quebra se você sourceou .o script. Nessas situações, $_conteria o último parâmetro do último comando que você executou antes do .. $BASH_SOURCEfunciona sempre.
clacke
11

Comparei muitas das respostas dadas e criei algumas soluções mais compactas. Eles parecem lidar com todos os casos extremos que surgem da sua combinação favorita de:

  • Caminhos absolutos ou relativos
  • Links flexíveis de arquivo e diretório
  • Invocação como script, bash script, bash -c script, source script, ou. script
  • Espaços, guias, novas linhas, unicode etc. nos diretórios e / ou nome do arquivo
  • Nomes de arquivos iniciados com um hífen

Se você estiver executando no Linux, parece que usar o procidentificador é a melhor solução para localizar a fonte totalmente resolvida do script em execução no momento (em uma sessão interativa, o link aponta para o respectivo /dev/pts/X):

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

Isso tem um pouco de feiura, mas a correção é compacta e fácil de entender. Não estamos usando apenas os primitivos do bash, mas estou bem com isso, porque readlinksimplifica a tarefa consideravelmente. Ele echo Xadiciona um Xao final da cadeia de caracteres variável, para que qualquer espaço em branco no nome do arquivo não seja consumido e a substituição de parâmetro ${VAR%X}no final da linha se livre do X. Como readlinkadiciona uma nova linha própria (que normalmente seria consumida na substituição de comando, se não fosse nossa artimanha anterior), também precisamos nos livrar disso. Isso é feito com mais facilidade usando o para representar novas linhas (também é assim que você pode criar facilmente diretórios e arquivos desonestos).$'' esquema de citações, que nos permite usar seqüências de escape, como\n

O item acima deve cobrir suas necessidades de localização do script atualmente em execução no Linux, mas se você não tiver o procsistema de arquivos à sua disposição ou se estiver tentando localizar o caminho totalmente resolvido de outro arquivo, talvez você encontre o código abaixo útil. É apenas uma pequena modificação do one-liner acima. Se você estiver brincando com nomes de diretório / arquivos estranhos, verifique a saída com ambos lse readlinké informativo, pois lsproduzirá caminhos "simplificados", substituindo ?coisas como novas linhas.

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"
billyjmc
fonte
Eu recebo o /dev/pts/30bash no Ubuntu 14.10 Desktop.
Dan Dascalescu 15/08/2015
@DanDascalescu Usando o one-liner? Ou o trecho de código completo na parte inferior? E você estava dando nomes de caminho complicados?
billyjmc
A uma linha mais uma linha para echo $resolved, eu salva-lo como d, chmod +x d, ./d.
Dan Dascalescu 20/08/2015
@DanDascalescu A primeira linha no seu script precisa ser#!/bin/bash
billyjmc
10

Tente usar:

real=$(realpath $(dirname $0))
eeerahul
fonte
1
Tudo o que eu quero saber é: por que esse caminho não é bom? Não parecia ruim e correto para mim. Alguém poderia explicar por que é votado?
Shou Ya
7
realpath não é um utilitário padrão.
Steve Bennett
2
No Linux, o realpath é um utilitário padrão (parte do pacote GNU coreutils), mas não é um bash embutido (isto é, uma função fornecida pelo próprio bash). Se você estiver executando o Linux, esse método provavelmente funcionará, embora eu substitua a opção $0para ${BASH_SOURCE[0]}que esse método funcione em qualquer lugar, inclusive em uma função.
Doug Richardson
3
A ordem das operações nesta resposta está errada. Você precisa primeiro resolver o link simbólico e, em seguida , fazer isso, dirnameporque a última parte de $0pode ser um link simbólico que aponta para um arquivo que não está no mesmo diretório que o próprio link simbólico. A solução descrita nesta resposta apenas obtém o caminho do diretório em que o link simbólico armazenava, não o diretório do destino. Além disso, esta solução está faltando citando. Não funcionará se o caminho contiver caracteres especiais.
hagello
3
dir="$(realpath "$(dirname "${BASH_SOURCE[0]}")")"
Kostiantyn Ponomarenko
9

Experimente a seguinte solução compatível com outros:

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

pois os comandos como realpathou readlinkpodem não estar disponíveis (dependendo do sistema operacional).

Nota: No Bash, é recomendável usar em ${BASH_SOURCE[0]}vez de $0, caso contrário, o caminho pode ser interrompido ao obter o arquivo ( source/ .).

Como alternativa, você pode tentar a seguinte função no bash:

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

Esta função leva 1 argumento. Se o argumento já tiver um caminho absoluto, imprima-o como está; caso contrário, imprima a $PWDvariável + argumento do nome do arquivo (sem ./prefixo).

Relacionado:

kenorb
fonte
Por favor, explique mais sobre a função realpath.
27415 Chris
1
A realpathfunção @Chris usa 1 argumento. Se o argumento já tiver um caminho absoluto, imprima como está, caso contrário, imprima $PWD+ nome do arquivo (sem ./prefixo).
kenorb
Sua solução compatível com outras não funciona quando o script é vinculado.
Jakub Jirutka
9

Eu acredito que eu tenho esse. Estou atrasado para a festa, mas acho que alguns apreciarão estar aqui se eles encontrarem esse tópico. Os comentários devem explicar:

#!/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
Geoff Nixon
fonte
8

Estas são maneiras curtas de obter informações de script:

Pastas e arquivos:

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

Usando estes comandos:

    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`

E eu consegui esta saída:

     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

Consulte também: https://pastebin.com/J8KjxrPF

User8461
fonte
Acho que minha resposta está boa, porque é difícil encontrar uma edição de trabalho simples. Aqui você pode pegar o código que você gosta, por exemplo, cd + pwd, dirname + realpath ou dirname + readlink. Não tenho certeza de que todas as partes existam antes e a maioria das respostas seja complexa e sobrecarregada. Aqui você pode descobrir o código que deseja usar. Pelo menos, não o remova conforme necessário no futuro: D
User8461
8

Isso funciona no bash-3.2:

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

Se você tem um ~/bindiretório no seu $PATH, você tem Adentro deste diretório. Ele origina o script ~/bin/lib/B. Você sabe onde o script incluído é relativo ao original, no libsubdiretório, mas não onde é relativo ao diretório atual do usuário.

Isso é resolvido pelo seguinte (dentro A):

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

Não importa onde o usuário está ou como ele chama o script, isso sempre funcionará.

Matt Tardiff
fonte
3
O ponto whiché muito discutível. type, hashE outros builtins fazer a mesma coisa melhor em bash. whiché um pouco mais portátil, embora realmente não seja o mesmo whichusado em outros shells como o tcsh, que o possui como um componente interno.
Reintegrar Monica Por favor,
"Sempre"? De modo nenhum. whichSendo uma ferramenta externa, você não tem motivos para acreditar que ela se comporta de forma idêntica ao shell pai.
Charles Duffy
7

A melhor solução compacta, na minha opinião, seria:

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

Não há confiança em nada além de Bash. O uso de dirname, readlinke basenameacabará por levar a problemas de compatibilidade, para que eles devem ser evitados, se possível.

AsymLabs
fonte
2
Você provavelmente deve adicionar barra para que: "$( cd "$( echo "${BASH_SOURCE[0]%/*}/" )"; pwd )". Você teria problemas com o diretório raiz, se não o fizer. Além disso, por que você precisa usar o eco?
Julio
dirnamee basenamesão padronizados para POSIX, então por que evitar usá-los? Links: dirname,basename
myrdd
Impedir dois garfos de processo extras e aderir aos embutidos do shell pode ser uma das razões.
Amit Naidu