Por que "cd" não funciona em um shell script?

767

Estou tentando escrever um pequeno script para alterar o diretório atual para o diretório do meu projeto:

#!/bin/bash
cd /home/tree/projects/java

Salvei este arquivo como proj, adicionei permissão de execução chmode copiei-o para /usr/bin. Quando eu chamo por proj:, não faz nada. O que estou fazendo errado?

ashokgelal
fonte
10
duplicado entre sites: superuser.com/questions/176783/…
lesmana
No futuro, você sempre pode tentar testá-lo pwdna última linha. Portanto, antes de terminar roteiro, então você pode verificar é trabalhar ou não ..
sobi3ch
5
@lesmana como isso é uma duplicata?
Aland
@aland Como o OP de fato não executa o script, é por isso que o diretório de trabalho não muda para ele. cdcomando funciona bem dentro de scripts, tente você mesmo.
Alexander Gonchiy

Respostas:

635

Os scripts de shell são executados dentro de um subshell, e cada subshell tem seu próprio conceito de qual é o diretório atual. O resultado cdé bem-sucedido, mas assim que o subshell sai, você volta ao shell interativo e nada muda lá.

Uma maneira de contornar isso é usar um alias:

alias proj="cd /home/tree/projects/java"
Greg Hewgill
fonte
7
Os aliases não são tão flexíveis para gerenciar ou alterar. No caso de muitos CDs, os scripts poderiam ser melhores.
Thevs
16
As funções são mais flexíveis do que aliases, então é nesse ponto que você procuraria a seguir quando os aliases não forem suficientes.
#
4
Vale a pena notar que no MS-DOS, o comportamento dos scripts era que um script chamado poderia alterar o diretório (e até a unidade) do shell de comando de chamada? E que o Unix não tem esse defeito?
Jonathan Leffler
23
Jonathan: enquanto isso é verdade, não está realmente relacionado à questão. As respostas no SO teriam o dobro do tempo se elas tivessem para cada lista as deficiências correspondentes no MS-DOS!
Greg Hewgill 04/11/2008
17
Outra maneira de contornar isso é obter o arquivo de script: . my-scriptor source my-script.
hellogoodbye
504

Você não está fazendo nada errado! Você alterou o diretório, mas apenas dentro do subshell que executa o script.

Você pode executar o script no seu processo atual com o comando "ponto":

. proj

Mas eu prefiro a sugestão de Greg de usar um pseudônimo neste caso simples.

Adam Liss
fonte
95
.também estiver escrito source, escolha o que achar mais memorável.
#
47
@Ephemient: Boa PointSource Isso explica por que workssource também prova que a preguiça-não necessidade-é muitas vezes a mãe de inventionsource
Adam Liss
8
@hemhemient: note que a fonte é usada no shell C e no Bash; não é suportado em shells POSIX ou Korn, nem no shell Bourne clássico.
Jonathan Leffler
2
"fonte" é usado em Z-shell
Nathan Moos
1
@AdamLiss Funciona muito bem, mas há uma desvantagem - de alguma forma, o comando source / dot desabilita a possibilidade de concluir automaticamente o nome do script / comando com a tecla TAB. Ainda é possível adicionar argumentos / entrada com a tecla TAB, mas infelizmente o nome do comando precisa ser inserido diretamente. Você sabe como fazer a tecla TAB funcionar nesse caso?
Rafal 23/01
215

O cdem seu script tecnicamente trabalhou como ele mudou o diretório do shell que executou o script, mas isso foi um processo separado bifurcada do seu shell interativo.

Uma maneira compatível com Posix para resolver esse problema é definir um procedimento de shell em vez de um script de comando invocado por shell .

jhome () {
  cd /home/tree/projects/java
}

Você pode digitar ou colocar em um dos vários arquivos de inicialização do shell.

DigitalRoss
fonte
6
Concordo, esta é a melhor resposta, esbarrou. Além disso, o alias pode ser apropriado em algumas situações, mas se ele estiver tentando usar o cd em um script e quiser adicionar mais alguma coisa, um alias seria inútil.
23713 Justin Buser
4
Parece uma solução! E eu estou tentando implementá-lo, mas ele não está executando :( aqui está o meu script: #! / Bin / bash jhome () {echo "please" cd / home echo "work"} jhome
Gant Laborde
1
@GantMan, você deve adicionar isso em "arquivos de inicialização do shell", como ~ / .bashrc
Jackie Yeh
3
Você também pode usar um argumento (ou dois ...), ou seja:jhome(){ cd /home/tree/projects/$1; }
irbanana 12/02/16
1
Esse deve ser o caminho certo, possibilitando o uso de caracteres especiais, como "-" por exemplo.
bangkokguy
159

Isso cdé feito dentro do shell do script. Quando o script termina, esse shell sai e você é deixado no diretório em que estava. "Origem" do script, não o execute. Ao invés de:

./myscript.sh

Faz

. ./myscript.sh

(Observe o ponto e o espaço antes do nome do script.)

Tzachi.e
fonte
2
Isso é legal e provavelmente bom saber. Mas o que o último faz exatamente (como funciona)? Esta é provavelmente a melhor solução para este segmento.
Jonah
2
fwiw, na seção de comentários da resposta de Adam Liss, ephemient responde à pergunta de qual é o '.' é. É a mesma coisa quesource
g19fanatic
1
Cuidado, "fonte" é um basismo. isto é, dash (padrão do Debian para / bin / sh) não o suporta, enquanto "." funciona como esperado.
allo
Ótima resposta! Além disso, se myscript.shestiver em um diretório incluído no $ PATH, você poderá obtê-lo de qualquer lugar sem especificar o caminho completo.
CodeBrew 6/02/19
Isso funcionou para mim o melhor. Esta solução é a mais simples (+1).
HuserB1989
96

Para criar um script bash que fará o cd para um diretório selecionado:

Crie o arquivo de script

#! / bin / sh
# file: / scripts / cdjava
#
cd / home / askgelal / projetos / java

Em seguida, crie um alias no seu arquivo de inicialização.

#! / bin / sh
# file /scripts/mastercode.sh
#
alias cdjava = '. / scripts / cdjava '

  • Criei um arquivo de inicialização em que despejo todos os meus aliases e funções personalizadas.
  • Depois, forneço esse arquivo no meu .bashrc para defini-lo em cada inicialização.

Por exemplo, crie um arquivo de aliases / funções mestre: /scripts/mastercode.sh
(coloque o alias neste arquivo.)

Em seguida, no final do seu arquivo .bashrc :

source /scripts/mastercode.sh



Agora é fácil fazer o cd no diretório java, basta digitar cdjava e você estará lá.

Matt Thomas
fonte
2
o arquivo mastercode.shnão precisa do shabang ( #!/bin/sh), pois não é (e não pode ser) executado em um subshell. Mas, ao mesmo tempo, você precisa documentar o "sabor" do shell desse arquivo; por exemplo, ksh ou bash (ou (t) csh / zsh, etc), e quase certamente não é realmente sh. Eu costumo adicionar um comentário (mas não o shebang) para comunicar isso; por exemplo, "este arquivo deve ser originado (do bash), não executado como um script de shell".
22613 Michael
Outra dica (para o bash): se você usar variáveis ​​em seu script, como o script está sendo sourced (via the alias), essas variáveis ​​vazarão no seu ambiente de shell. Para evitar isso, faça todo o trabalho do script em uma função e chame-o no final do script. Dentro da função, declare quaisquer variáveis local.
Rhubbarb 17/04/2015
no ubuntu basta criar ~ / .bash_aliases (se já não existir). Em seguida, basta adicionar seus apelidos lá, reiniciar o terminal e pronto.
Vlad
51

Você pode usar .para executar um script no ambiente atual do shell:

. script_name

ou, alternativamente, seu alias mais legível, mas específico do shell source:

source script_name

Isso evita o subshell e permite que quaisquer variáveis ​​ou componentes internos (inclusive cd) afetem o shell atual.

Sagar
fonte
38

A idéia de Jeremy Ruten de usar um link simbólico desencadeou um pensamento que não passou por nenhuma outra resposta. Usar:

CDPATH=:$HOME/projects

O cólon principal é importante; significa que, se houver um diretório 'dir' no diretório atual, ' cd dir' será alterado para isso, em vez de sair para outro lugar. Com o valor definido como mostrado, você pode:

cd java

e, se não houver um subdiretório chamado java no diretório atual, ele o levará diretamente para $ HOME / projects / java - sem alias, sem scripts, sem executivos duvidosos ou comandos de ponto.

Meu $ HOME é / Users / jleffler; meu $ CDPATH é:

:/Users/jleffler:/Users/jleffler/mail:/Users/jleffler/src:/Users/jleffler/src/perl:/Users/jleffler/src/sqltools:/Users/jleffler/lib:/Users/jleffler/doc:/Users/jleffler/work
Jonathan Leffler
fonte
31

Use exec bashno final

Um script bash opera em seu ambiente atual ou em seus filhos, mas nunca em seu ambiente pai.

No entanto, essa pergunta geralmente é feita porque alguém deseja ser deixado em um (novo) prompt do bash em um determinado diretório após a execução de um script bash de outro diretório.

Se for esse o caso, basta executar uma instância do bash filho no final do script:

#!/usr/bin/env bash
cd /home/tree/projects/java
exec bash
Serge Stroobandt
fonte
16
Isso cria um novo subshell. Ao digitar, exitvocê retornará ao shell em que executou esse script.
Tripleee
1
Quando o script foi chamado várias vezes, parece criar shells aninhados e preciso digitar 'exit' várias vezes. Isso poderia ser resolvido de alguma forma?
VladSavitsky
Veja as outras respostas: use um alias, use "pushd / popd / dirs" ou use "source"
qneill 03/02
25

Eu tenho meu código para trabalhar usando. <your file name>

./<your file name> Dose não funciona porque não altera seu diretório no terminal, apenas altera o diretório específico para esse script.

Aqui está o meu programa

#!/bin/bash 
echo "Taking you to eclipse's workspace."
cd /Developer/Java/workspace

Aqui está o meu terminal

nova:~ Kael$ 
nova:~ Kael$ . workspace.sh
Taking you to eclipe's workspace.
nova:workspace Kael$ 
kaelhop
fonte
2
Qual é a diferença entre . somethinge ./something?? Essa resposta funcionou para mim e não entendo o porquê.
Dracorat 02/11/2012
. somethingpermite-lhe executar o roteiro de qualquer lugar, ./somethingexige que você para estar no diretório do arquivo é armazenado no.
Projjol
Você pode colocar o ponto na linha shebang? #!. /bin/bash?
Sigfried 19/04
20

basta executar:

cd /home/xxx/yyy && command_you_want
Warhansen
fonte
Isso funcionou para mim graças @warhansen
Ashish Kumar
12

Quando você aciona um script de shell, ele executa uma nova instância desse shell ( /bin/bash). Assim, seu script apenas dispara um shell, altera o diretório e sai. Em cdoutras palavras , (e outros comandos desse tipo) em um script de shell não afetam nem têm acesso ao shell a partir do qual foram iniciados.

Daniel Spiewak
fonte
11

No meu caso particular, eu precisei muitas vezes para alterar para o mesmo diretório. Então, no meu .bashrc (eu uso o ubuntu), adicionei o

1 -

$ nano ~ / bashrc

 function switchp
 {
    cd /home/tree/projects/$1
 }

2-

$ fonte ~ / .bashrc

3 -

$ switchp java

Diretamente ele fará: cd / home / tree / projects / java

Espero que ajude!

sonhador
fonte
1
@WM Você pode, eventualmente, fazer "$ switchp" sem qualquer parâmetro para ir diretamente para a árvore do projeto
workdreamer
2
@workdreamer A abordagem de função que você descreveu acima resolveu um pequeno problema aqui ao entrar em subpastas que possuem a mesma pasta pai no meu sistema. Obrigado.
WM
10

Você pode fazer o seguinte:

#!/bin/bash
cd /your/project/directory
# start another shell and replacing the current
exec /bin/bash

EDIT: Isso também pode ser 'pontilhado', para impedir a criação de shells subsequentes.

Exemplo:

. ./previous_script  (with or without the first line)
Thevs
fonte
1
Isso fica um pouco confuso após algumas execuções. Você precisará exit(ou ctrl + d) várias vezes para sair do shell, por exemplo. Um alias é muito mais limpo (mesmo que o comando shell produza um diretório e ele para a saída - alias something = "cd getnewdirectory.sh")
dbr
Observe o 'exec'. Faz substituir de casca velha.
Thevs
2
O exec substitui apenas o sub-shell que estava executando o comando cd, não o shell que executou o script. Se você tivesse pontilhado o script, estaria correto.
Jonathan Leffler
Faça com que seja pontilhado - não há problema. Eu apenas propus uma solução. Não depende de como você inicia isso.
Thevs
2
Hehe, eu acho que é melhor apenas para 'dot' script de uma linha com apenas o comando cd :) Vou manter a minha resposta de qualquer maneira ... Isso vai ser a resposta estúpida correta para pergunta estúpida incorreta :)
Thevs
7

Ele altera apenas o diretório do próprio script, enquanto o diretório atual permanece o mesmo.

Você pode usar um link simbólico . Ele permite que você faça um "atalho" para um arquivo ou diretório, então você só precisa digitar algo parecido cd my-project.

Jeremy Ruten
fonte
Ter esse link simbólico em todos os diretórios seria um incômodo. Seria possível colocar o link simbólico em $ HOME e depois 'cd ~ / my-project'. Francamente, porém, é mais simples usar o CDPATH.
Jonathan Leffler
7

Você pode combinar o alias de Adam & Greg e as abordagens de pontos para criar algo que possa ser mais dinâmico.

alias project=". project"

Agora, a execução do alias do projeto executará o script do projeto no shell atual, em oposição ao subshell.

robertmoggach
fonte
Era exatamente isso que eu precisava para manter todo o meu script e apenas chamá-lo do bash e manter a sessão atual - esta é a resposta PERFEITA.
Matt A Ninja
7

Você pode combinar um alias e um script,

alias proj="cd \`/usr/bin/proj !*\`"

desde que o script faça eco no caminho de destino. Observe que esses são backticks que cercam o nome do script. 

Por exemplo, seu script pode ser

#!/bin/bash
echo /home/askgelal/projects/java/$1

A vantagem dessa técnica é que o script pode pegar qualquer número de parâmetros de linha de comando e emitir destinos diferentes calculados por uma lógica possivelmente complexa.

JA Faucett
fonte
3
porque? você pode simplesmente usar: proj() { cd "/home/user/projects/java/$1"; }=> proj "foo"(ou, proj "foo bar"<= no caso de você ter espaços) ... ou até mesmo (por exemplo): proj() { cd "/home/user/projects/java/$1"; shift; for d; do cd "$d"; done; }=> proj a b c=> faz um cdinto/home/user/projects/java/a/b/c
Michael
5

No seu arquivo ~ / .bash_profile. adicione a próxima função

move_me() {
    cd ~/path/to/dest
}

Reinicie o terminal e você pode digitar

move_me 

e você será movido para a pasta de destino.

mihai.ciorobea
fonte
3

Você pode usar o operador &&:

cd myDirectory && ls

Jack Bauer
fonte
Downvote: Isso não tenta responder à pergunta real aqui. Você pode executar dois comandos no prompt (com &&se desejar que o segundo seja condicional no primeiro, ou apenas com ;ou nova linha entre eles), mas colocar isso em um script o levará de volta a "por que o shell pai não usa" o novo diretório quando o cdfoi realmente bem-sucedido? "
Tripleee
3

Embora a fonte do script que você deseja executar seja uma solução, você deve estar ciente de que esse script pode modificar diretamente o ambiente do seu shell atual. Também não é mais possível passar argumentos.

Outra maneira de fazer isso é implementar seu script como uma função no bash.

function cdbm() {
  cd whereever_you_want_to_go
  echo "Arguments to the functions were $1, $2, ..."
}

Essa técnica é usada pelo autojump: http://github.com/joelthelion/autojump/wiki para fornecer a você os indicadores de diretório de shell de aprendizado.

thomasd
fonte
3

Você pode criar uma função como abaixo na sua .bash_profilee ela funcionará sem problemas.

A função a seguir aceita um parâmetro opcional que é um projeto. Por exemplo, você pode simplesmente executar

cdproj

ou

cdproj project_name

Aqui está a definição da função.

cdproj(){
    dir=/Users/yourname/projects
    if [ "$1" ]; then
      cd "${dir}/${1}"
    else
      cd "${dir}"
    fi
}

Não se esqueça de adquirir seu .bash_profile

Krish
fonte
1
resposta muito melhor do que o apelido #
Pax0r 28/10/16
Esta solução é brilhante em sua simplicidade e flexibilidade.
Kjartan
3

Isso deve fazer o que você quiser. Vá para o diretório de interesse (de dentro do script) e crie um novo shell bash.

#!/bin/bash

# saved as mov_dir.sh
cd ~/mt/v3/rt_linux-rt-tools/
bash

Se você executar isso, ele o levará ao diretório de interesse e, quando você o sair, o levará de volta ao local original.

root@intel-corei7-64:~# ./mov_dir.sh

root@intel-corei7-64:~/mt/v3/rt_linux-rt-tools# exit
root@intel-corei7-64:~#

Isso levará você a voltar ao diretório original quando sair ( CTRL+ d)

jithu83
fonte
3
Desovar uma nova festança significa que você perderá sua história e ela começará de novo.
Tisch
2

LOOOOOng depois, mas fiz o seguinte:

crie um arquivo chamado case

cole o seguinte no arquivo:

#!/bin/sh

cd /home/"$1"

salve-o e depois:

chmod +x case

Eu também criei um alias no meu .bashrc:

alias disk='cd /home/; . case'

agora quando digito:

case 12345

essencialmente eu estou digitando:

cd /home/12345

Você pode digitar qualquer pasta após 'case':

case 12

case 15

case 17

que é como digitar:

cd /home/12

cd /home/15

cd /home/17

respectivamente

No meu caso, o caminho é muito mais longo - esses caras resumiram o ~ info anteriormente.

chris
fonte
1
O cdno alias é supérfluo e deselegante; o apelido deve simplesmente '. ~ / case`. Também caseé uma palavra-chave reservada, portanto, uma escolha bastante ruim para um nome.
Tripleee 29/07
1

Você não precisa de script, apenas defina a opção correta e crie uma variável de ambiente.

shopt -s cdable_vars

no seu ~/.bashrcpermite ao cdconteúdo de variáveis ​​de ambiente.

Crie uma variável de ambiente:

export myjava="/home/tree/projects/java"

e você pode usar:

cd myjava

Outras alternativas .

Gauthier
fonte
0

Se você estiver usando peixe como casca, a melhor solução é criar uma função. Como exemplo, dada a pergunta original, você pode copiar as 4 linhas abaixo e colá-las na linha de comando do peixe:

function proj
   cd /home/tree/projects/java
end
funcsave proj

Isso criará a função e a salvará para uso posterior. Se o seu projeto mudar, basta repetir o processo usando o novo caminho.

Se preferir, você pode adicionar manualmente o arquivo de função, fazendo o seguinte:

nano ~/.config/fish/functions/proj.fish

e digite o texto:

function proj
   cd /home/tree/projects/java
end

e, finalmente, pressione ctrl + x para sair ey seguido de return para salvar suas alterações.

( NOTA: o primeiro método de usar o funcsave cria o arquivo proj.fish para você ).

Lane Roathe
fonte
0

Eu tenho um script bash simples chamado p para gerenciar a alteração de diretório no
github.com/godzilla/bash-stuff,
basta colocar o script no diretório bin local (/ usr / local / bin)
e colocar

alias p='. p'

no seu .bashrc

Godzilla
fonte
o que isso faz? parece que ele iria de forma recursiva executar-se, embora eu tenho certeza que se você executá-lo antes que ele não o faz;)
Ryan Taylor
0

É uma pergunta antiga, mas estou realmente surpreso por não ver esse truque aqui

Em vez de usar cd, você pode usar

export PWD=the/path/you/want

Não há necessidade de criar subshells ou usar aliases.

Observe que é sua responsabilidade garantir que o / path / you / want exista.

Yuri Nudelman
fonte
Infelizmente não funcionou.
ttfreeman 03/04
0

Conforme explicado nas outras respostas, você alterou o diretório, mas apenas dentro do sub-shell que executa o script . isso não afeta o shell pai.

Uma solução é usar funções bash em vez de um script bash ( sh); colocando seu código de script bash em uma função. Isso torna a função disponível como um comando e, em seguida, isso é executado sem um processo filho e, portanto, qualquer cdcomando afeta o shell do chamador.

Funções do Bash:

Um recurso do perfil bash é armazenar funções personalizadas que podem ser executadas no terminal ou em scripts bash da mesma maneira que você executa aplicativos / comandos. Isso também pode ser usado como um atalho para comandos longos.

Para tornar seu sistema eficiente de funções amplamente, você precisará copiar sua função no final de vários arquivos

/home/user/.bashrc
/home/user/.bash_profile
/root/.bashrc
/root/.bash_profile

Você pode sudo kwrite /home/user/.bashrc /home/user/.bash_profile /root/.bashrc /root/.bash_profileeditar / criar esses arquivos rapidamente

Como :

Copie o código do seu script bash dentro de uma nova função no final do arquivo de perfil do bash e reinicie o terminal. Depois, você pode executar cddou qualquer que seja a função que você escreveu.

Exemplo de script

Fazendo atalho para cd ..comcdd

cdd() {
  cd ..
}

ls atalho

ll() {
  ls -l -h
}

ls atalho

lll() {
  ls -l -h -a
}
intika
fonte
-2

Você pode executar algumas linhas no mesmo subshell se terminar as linhas com barra invertida.

cd somedir; \
pwd
Máx.
fonte