Perché "cd" non funziona in uno script di shell?


767

Sto cercando di scrivere un piccolo script per cambiare la directory corrente nella directory del mio progetto:

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

Ho salvato questo file come proj, ho aggiunto il permesso di esecuzione chmode l'ho copiato /usr/bin. Quando lo chiamo per: projnon fa nulla. Che cosa sto facendo di sbagliato?


10

In futuro puoi sempre provare con pwdl'ultima riga. Quindi, prima che finisca lo script, puoi verificare se funziona o no ..
sobi3ch

5
@lesmana come è un duplicato?
aland

@aland Perché OP fa non infatti eseguire lo script, è per questo che la directory di lavoro non cambia per lui. cdIl comando funziona bene all'interno degli script, provalo tu stesso.
Alexander Gonchiy,

Risposte:


635

Gli script della shell vengono eseguiti all'interno di una subshell e ogni subshell ha il proprio concetto di quale sia la directory corrente. Il cdriesce, ma non appena le uscite subshell, sei tornata nella shell interattiva e nulla mai ci cambiato.

Un modo per aggirare questo è invece usare un alias:

alias proj="cd /home/tree/projects/java"

7
Gli alias non sono così flessibili da gestire o modificare. Nel caso di molti cd, gli script potrebbero essere migliori.
Il

16
Le funzioni sono più flessibili degli alias, quindi è lì che guarderesti quando gli alias non sono sufficienti.
effimero

4
Vale la pena notare che su MS-DOS, il comportamento degli script era che uno script chiamato poteva cambiare la directory (e persino guidare) della shell dei comandi chiamanti? E che Unix non ha questo difetto?
Jonathan Leffler,

23
Jonathan: anche se questo è vero, non è realmente correlato alla domanda. Le risposte su SO otterrebbero il doppio del tempo se dovessero elencare ciascuna delle carenze corrispondenti in MS-DOS!
Greg Hewgill,

17
Un altro modo per aggirare il problema consiste nell'origine del file di script: . my-scripto source my-script.
Ciao Arrivederci,

504

Non stai facendo niente di male! Hai modificato la directory, ma solo all'interno della subshell che esegue lo script.

È possibile eseguire lo script nel processo corrente con il comando "punto":

. proj

Ma preferirei il suggerimento di Greg di usare un alias in questo semplice caso.


95
.è anche scritto source, scegli quello che ritieni più memorabile.
effimero

47
@Ephemient: buona fonte di punti Questo spiega perché funziona fonte Inoltre dimostra che la pigrizia, non la necessità, è spesso la madre della fonte di invenzioni
Adam Liss,

8
@ephemient: nota che il sorgente è usato in C shell e Bash; non è supportato nelle shell POSIX o Korn, né nella classica shell Bourne.
Jonathan Leffler,

2
"source" è usato in Z-shell
Nathan Moos

1
@AdamLiss Funziona alla grande, ma c'è un inconveniente: in qualche modo il comando source / dot disabilita la possibilità di completare automaticamente il nome dello script / comando con il tasto TAB. È ancora possibile aggiungere argomenti / input con il tasto TAB, ma sfortunatamente il nome del comando deve essere inserito direttamente. Sai come far funzionare la chiave TAB in questo caso?
Rafal,

215

Il cdnello script tecnicamente lavorato in quanto cambiava la directory della shell che eseguiva lo script, ma quello era un processo separato biforcuto dalla tua shell interattiva.

Un modo compatibile con Posix per risolvere questo problema consiste nel definire una procedura shell anziché uno script di comandi invocato dalla shell .

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

Puoi semplicemente digitare questo o inserirlo in uno dei vari file di avvio della shell.


6
Sono d'accordo, questa è la risposta migliore, sbarrata. Inoltre, l'alias può essere appropriato in alcune situazioni, ma se sta cercando di usare cd in uno script e volesse aggiungere qualcos'altro, un alias sarebbe inutile.
Justin Buser,

4
Questa sembra una soluzione! E sto cercando di implementarlo, ma non funziona :( ecco il mio script: #! / Bin / bash jhome () {echo "per favore" cd / home echo "lavoro"} jhome
Gant Laborde,

1
@GantMan, dovresti aggiungerlo in un "file di avvio della shell", come ~ / .bashrc
Jackie Yeh

3
Puoi anche usare un argomento (o due ...), cioè:jhome(){ cd /home/tree/projects/$1; }
irbanana,

1
Questo dovrebbe essere il modo giusto, questo rende possibile usare caratteri speciali, come "-" per esempio.
bangkokguy,

159

Il cdavviene entro guscio della sceneggiatura. Quando lo script termina, quella shell si chiude e quindi si rimane nella directory in cui ci si trovava. "Fonte" lo script, non eseguirlo. Invece di:

./myscript.sh

fare

. ./myscript.sh

(Nota il punto e lo spazio prima del nome dello script.)


2
È bello e probabilmente buono a sapersi. Cosa fa esattamente quest'ultimo (come funziona)? Questa è probabilmente la migliore soluzione su questo thread.
Giona

2
In seguito, nella sezione commenti della risposta di Adam Liss, gli effimeri rispondono alla domanda su cosa sia il "." è. È la stessa cosa disource
g19fanatic

1
Fai attenzione, "fonte" è un bashismo. ie trattino (predefinito Debian per / bin / sh) non lo supporta, mentre "." funziona come previsto.
allo

Bella risposta! anche se si myscript.shtrova in una directory inclusa in $ PATH, è possibile cercarlo da qualsiasi luogo senza specificare il percorso completo.
CodeBrew

Questo ha funzionato per me al meglio. Questa soluzione è la più semplice (+1).
HuserB1989

96

Per creare uno script bash che verrà inserito in una directory selezionata:

Crea il file di script

#! / Bin / sh
# file: / scripts / cdjava
#
cd / home / askgelal / progetti / java

Quindi creare un alias nel file di avvio.

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

  • Ho creato un file di avvio in cui ho scaricato tutti i miei alias e funzioni personalizzate.
  • Quindi sorgente questo file nel mio .bashrc per averlo impostato su ogni avvio.

Ad esempio, creare un file alias / funzioni master: /scripts/mastercode.sh
(inserire l'alias in questo file.)

Quindi alla fine del tuo .bashrc file :

fonte /scripts/mastercode.sh



Ora è facile da cd nella tua directory java, basta digitare cdjava e sei lì.


2
il file mastercode.shnon ha bisogno di shabang ( #!/bin/sh), poiché non è (e non può essere) eseguito in una subshell. Ma allo stesso tempo, è necessario documentare il "sapore" della shell di questo file; ad esempio, ksh o bash (o (t) csh / zsh, ecc.), e quasi certamente non lo è sh. Di solito aggiungo un commento (ma non lo shebang) per comunicarlo; ad esempio, "questo file deve essere di provenienza (da bash), non eseguito come script di shell".
michael,

Un altro suggerimento (per bash): se usi le variabili nel tuo script, allora mentre lo script viene sourced (tramite il alias), quelle variabili coleranno nel tuo ambiente shell. Per evitarlo, esegui tutto il lavoro dello script in una funzione e chiamalo alla fine dello script. All'interno della funzione, dichiarare qualsiasi variabile local.
Rabarbaro

in Ubuntu basta creare ~ / .bash_aliases (se non esiste già). Quindi aggiungi i tuoi alias lì, riavvia il terminale e il gioco è fatto.
Vlad

51

È possibile utilizzare .per eseguire uno script nell'ambiente shell corrente:

. script_name

o in alternativa, il suo alias più leggibile ma specifico per shell source:

source script_name

Ciò evita la subshell e consente invece a qualsiasi variabile o builtin (incluso cd) di influire sulla shell corrente.


38

L'idea di Jeremy Ruten di usare un collegamento simbolico ha scatenato un pensiero che non ha incrociato altre risposte. Uso:

CDPATH=:$HOME/projects

Il colon principale è importante; significa che se c'è una directory 'dir' nella directory corrente, allora ' cd dir' cambierà in quella, piuttosto che saltare da qualche altra parte. Con il valore impostato come mostrato, puoi fare:

cd java

e, se non vi è alcuna sottodirectory chiamata java nella directory corrente, allora vi porterà direttamente a $ HOME / progetti / java - nessun alias, nessuno script, nessun exec o comandi punto dubbi.

My $ HOME è / Users / jleffler; il mio $ 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

31

Utilizzare exec bashalla fine

Uno script bash opera nel suo ambiente attuale o in quello dei suoi figli, ma mai nel suo ambiente genitore.

Tuttavia, questa domanda viene spesso posta perché si desidera essere lasciati al prompt (nuovo) bash in una determinata directory dopo l'esecuzione di uno script bash da un'altra directory.

In tal caso, eseguire semplicemente un'istanza bash figlio alla fine dello script:

#!/usr/bin/env bash
cd /home/tree/projects/java
exec bash

16
Questo crea una nuova subshell. Quando digiti exit, tornerai alla shell in cui hai eseguito questo script.
Tripleee,

1
Quando lo script viene chiamato più volte sembra che crea shell nidificate e devo digitare "exit" molte volte. Questo potrebbe essere risolto in qualche modo?
VladSavitsky,

Vedi le altre risposte: usa un alias, usa "pushd / popd / dirs" o usa "source"
qneill

25

Ho ottenuto il mio codice per funzionare utilizzando. <your file name>

./<your file name> la dose non funziona perché non cambia la directory nel terminale ma cambia solo la directory specifica per quello script.

Ecco il mio programma

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

Ecco il mio terminale

nova:~ Kael$ 
nova:~ Kael$ . workspace.sh
Taking you to eclipe's workspace.
nova:workspace Kael$ 

2
Qual è la differenza tra . somethinge ./something?? Questa risposta ha funzionato per me e non capisco perché.
Dracorat,

. somethingconsente di eseguire lo script da qualsiasi posizione, ./somethingrichiede di trovarsi nella directory in cui è archiviato il file.
Projjol


Puoi mettere il punto nella linea shebang? #!. /bin/bash?
Sigfried


12

Quando si attiva uno script di shell, viene eseguita una nuova istanza di quella shell ( /bin/bash). Pertanto, il tuo script attiva solo una shell, cambia la directory ed esce. In altri termini cd(e altri comandi simili) all'interno di uno script di shell non influiscono né hanno accesso alla shell da cui sono stati lanciati.


11

Nel mio caso particolare avevo bisogno di troppe volte per cambiare per la stessa directory. Quindi sul mio .bashrc (io uso Ubuntu) ho aggiunto il

1 -

$ nano ~. / bashrc

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

2-

$ source ~ / .bashrc

3 -

$ switchp java

Direttamente farà: cd / home / tree / projects / java

Spero che aiuti!


1
@WM Alla fine puoi fare "$ switchp" senza alcun parametro per andare direttamente all'albero del progetto
workdreamer,

2
@workdreamer L'approccio alla funzione che hai descritto sopra ha risolto un piccolo problema in questo caso andando nelle sottocartelle che hanno la stessa cartella principale sul mio sistema. Grazie.
WM,

10

Puoi fare quanto segue:

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

EDIT: anche questo potrebbe essere 'punteggiato', per impedire la creazione di shell successive.

Esempio:

. ./previous_script  (with or without the first line)

1
Questo diventa piuttosto disordinato dopo alcune esecuzioni. Dovrai exit(o ctrl + d) più volte per uscire dalla shell, per esempio .. Un alias è molto più pulito (anche se il comando shell genera una directory, e all'output - alias qualcosa = "cd getnewdirectory.sh")
dbr

Nota 'exec'. Fa sostituire il vecchio guscio.
Il

2
L'execut sostituisce solo la sotto-shell che stava eseguendo il comando cd, non la shell che eseguiva lo script. Se avessi punteggiato la sceneggiatura, allora avresti ragione.
Jonathan Leffler,

Renderlo 'punteggiato' - nessun problema. Ho appena proposto una soluzione. Non dipende da come lo lanci.
Thevs

2
Hehe, penso che sia meglio solo 'punteggiare' lo script di una riga con solo il comando cd :) Terrò comunque la mia risposta ... Sarà una risposta stupida corretta a una domanda stupida errata :)
Thevs

7

Cambia solo la directory per lo script stesso, mentre la directory corrente rimane la stessa.

È possibile che si desideri utilizzare un collegamento simbolico . Ti consente di creare un "collegamento" a un file o una directory, quindi dovresti solo digitare qualcosa del genere cd my-project.


Avere quel link simbolico in ogni directory sarebbe un fastidio. Sarebbe possibile mettere il link simbolico in $ HOME e quindi fare 'cd ~ / my-project'. Francamente, però, è più semplice usare CDPATH.
Jonathan Leffler,

7

Puoi combinare l'alias di Adam e Greg e gli approcci a punti per creare qualcosa che può essere più dinamico:

alias project=". project"

A questo punto, l'esecuzione dell'alias del progetto eseguirà lo script del progetto nella shell corrente anziché nella subshell.


Questo è esattamente ciò di cui avevo bisogno per mantenere tutto il mio script e chiamarlo da bash e mantenere la sessione corrente: questa è la risposta PERFETTA.
Matt The Ninja,

7

Puoi combinare un alias e uno script,

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

a condizione che lo script riprenda il percorso di destinazione. Si noti che si tratta di backtick che circondano il nome dello script. 

Ad esempio, la tua sceneggiatura potrebbe essere

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

Il vantaggio di questa tecnica è che lo script potrebbe accettare qualsiasi numero di parametri della riga di comando ed emettere destinazioni diverse calcolate da una logica forse complessa.


3
perché? potresti semplicemente usare: proj() { cd "/home/user/projects/java/$1"; }=> proj "foo"(o, proj "foo bar"<= nel caso tu abbia spazi) ... o anche (per esempio): proj() { cd "/home/user/projects/java/$1"; shift; for d; do cd "$d"; done; }=> proj a b c=> fa un cdin/home/user/projects/java/a/b/c
michael


5

Nel tuo file ~ / .bash_profile. aggiungi la prossima funzione

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

Riavvia il terminale e puoi digitare

move_me 

e verrai spostato nella cartella di destinazione.


3

Puoi usare l'operatore &&:

cd myDirectory && ls


Downvote: questo non tenta di rispondere alla domanda effettiva qui. È possibile eseguire due comandi al prompt (con &&se si desidera che il secondo sia condizionato dal primo, oppure solo con ;o newline tra di loro) ma inserendo questo in uno script si tornerà a "perché la shell padre non usa la nuova directory quando ha cdavuto successo? "
Tripleee,

3

Mentre l'approvvigionamento dello script che si desidera eseguire è una soluzione, è necessario tenere presente che questo script può quindi modificare direttamente l'ambiente della shell corrente. Inoltre non è più possibile passare argomenti.

Un altro modo di fare è implementare il tuo script come funzione in bash.

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

Questa tecnica viene utilizzata da autojump: http://github.com/joelthelion/autojump/wiki per fornire i segnalibri della directory di shell di apprendimento.


3

Puoi creare una funzione come di seguito nel tuo .bash_profile e funzionerà senza problemi.

La seguente funzione accetta un parametro opzionale che è un progetto. Ad esempio, puoi semplicemente correre

cdproj

o

cdproj project_name

Ecco la definizione della funzione.

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

Non dimenticare di procurarti il ​​tuo .bash_profile


1
risposta molto migliore
dell'alias

Questa soluzione è geniale nella sua semplicità e flessibilità.
Kjartan,

3

Questo dovrebbe fare quello che vuoi. Passare alla directory di interesse (dall'interno dello script), quindi generare una nuova shell bash.

#!/bin/bash

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

Se lo esegui, ti porterà alla directory di interesse e quando lo esci ti riporterà nella posizione originale.

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

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

Questo ti riporterà anche alla directory originale quando esci ( CTRL+ d)


3
Generare un nuovo bash ti farà perdere la tua storia e ricomincerà da capo.
Tisch,

2

LOOOOOng tempo dopo, ma ho fatto quanto segue:

crea un file chiamato case

incolla quanto segue nel file:

#!/bin/sh

cd /home/"$1"

salvalo e poi:

chmod +x case

Ho anche creato un alias nel mio .bashrc:

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

ora quando scrivo:

case 12345

essenzialmente sto scrivendo:

cd /home/12345

Puoi digitare qualsiasi cartella dopo 'case':

case 12

case 15

case 17

che è come scrivere:

cd /home/12

cd /home/15

cd /home/17

rispettivamente

Nel mio caso il percorso è molto più lungo - questi ragazzi lo hanno riassunto con le ~ informazioni in precedenza.


1
L' cdalias è superfluo e inelegante; l'alias dovrebbe semplicemente '. ~ / case` invece. Inoltre caseè una parola chiave riservata, quindi una scelta piuttosto scadente per un nome.
tripla il

1

Non hai bisogno di script, imposta solo l'opzione corretta e crea una variabile d'ambiente.

shopt -s cdable_vars

nel tuo ~/.bashrcconsente di cdal contenuto delle variabili di ambiente.

Crea una tale variabile d'ambiente:

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

e puoi usare:

cd myjava

Altre alternative .


0

Se stai usando il pesce come guscio, la soluzione migliore è creare una funzione. Ad esempio, data la domanda originale, è possibile copiare le 4 righe sottostanti e incollarle nella riga di comando del pesce:

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

Questo creerà la funzione e la salverà per usarla in seguito. Se il tuo progetto cambia, ripeti il ​​processo usando il nuovo percorso.

Se preferisci, puoi aggiungere manualmente il file di funzione procedendo come segue:

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

e inserisci il testo:

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

e infine premi ctrl + x per uscire e y seguito da invio per salvare le modifiche.

( NOTA: il primo metodo di utilizzo di funcsave crea il file proj.fish per te ).


0

Ho un semplice script bash chiamato p per gestire il cambio di directory su
github.com/godzilla/bash-stuff,
basta inserire lo script nella directory bin locale (/ usr / local / bin)
e mettere

alias p='. p'

nel tuo .bashrc


cosa fa questo? sembra che ricorrerebbe in modo ricorsivo, anche se sono sicuro che l'avrai eseguito prima che non lo faccia;)
Ryan Taylor


0

È una vecchia domanda, ma sono davvero sorpreso di non vedere questo trucco qui

Invece di usare cd puoi usare

export PWD=the/path/you/want

Non è necessario creare sottotitoli o utilizzare alias.

Nota che è tua responsabilità assicurarti che il / percorso / tu / desideri esista.


Purtroppo non ha funzionato.
ttfreeman,

0

Come spiegato nelle altre risposte, hai modificato la directory, ma solo all'interno della sotto-shell che esegue lo script . questo non ha alcun impatto sulla shell padre.

Una soluzione è usare le funzioni bash invece di uno script bash ( sh); inserendo il codice dello script bash in una funzione. Ciò rende disponibile la funzione come comando e quindi verrà eseguita senza un processo figlio e quindi qualsiasi cdcomando avrà un impatto sulla shell del chiamante.

Funzioni Bash:

Una caratteristica del profilo bash è l'archiviazione di funzioni personalizzate che possono essere eseguite nel terminale o negli script bash nello stesso modo in cui si eseguono applicazioni / comandi, questo potrebbe anche essere usato come scorciatoia per comandi lunghi.

Per rendere ampiamente efficiente il tuo sistema di funzioni dovrai copiare la tua funzione alla fine di diversi file

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

È possibile sudo kwrite /home/user/.bashrc /home/user/.bash_profile /root/.bashrc /root/.bash_profilemodificare / creare rapidamente quei file

Come :

Copia il codice dello script bash all'interno di una nuova funzione alla fine del file del profilo di bash e riavvia il terminale, quindi puoi eseguire cddo qualunque sia la funzione che hai scritto.

Esempio di script

Scorciatoia cd ..concdd

cdd() {
  cd ..
}

È una scorciatoia

ll() {
  ls -l -h
}

È una scorciatoia

lll() {
  ls -l -h -a
}

-2

È possibile eseguire alcune righe nella stessa subshell se si terminano le linee con barra rovesciata.

cd somedir; \
pwd
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.