Unable to use cd within a bash script as intended - bash

Consider this script I wrote, which should go into parent directory, when no argument is given (the if ... part).
#/bin/bash
if (($# == 0))
then
cd ..
else
for basename
do
cd ${PWD%$basename*}$basename
done
fi
The problem is, that if I execute it like this
./up.sh
the cd is executed in a subshell, rendering it useless.
If I execute the script using source, it works, but I don't want to call it that way (I makes calling the script to complicated, also you would expect to call it directly if found in the PATH).

An arbitrary program (such as your bash program) cannot change the working directory of the parent process, as that would pretty much break all existing processes that spawn children.
You should define a bash alias or function instead. As you have discovered, typing source ./up.sh (or shorter: . ./up.sh) works too.

I suggest using a function instead of a script
function myscript()
{
// use $1, $2, "$#" as usual in scripts
local v1="bla" # can use globals
export PATH="$PATH" # but global shell env too
somedirectory=..
cd $somedirectory
}
Alternatively, alias would work (but it doesn't support redirection, argument passing, flow control etc well, and you cannot nest them in $() IIRC).
Lastly source the existing script in the current shell like so:
source ./script.sh
ksh and bash have shorthands for that:
. ./script.sh
Beware of scripts with 'exit' statements though: they will exit the parent shell!

Related

How to pass absolute path as an argument in shell script? [duplicate]

I'm trying to write a small script to change the current directory to my project directory:
#!/bin/bash
cd /home/tree/projects/java
I saved this file as proj, added execute permission with chmod, and copied it to /usr/bin. When I call it by:
proj, it does nothing. What am I doing wrong?
Shell scripts are run inside a subshell, and each subshell has its own concept of what the current directory is. The cd succeeds, but as soon as the subshell exits, you're back in the interactive shell and nothing ever changed there.
One way to get around this is to use an alias instead:
alias proj="cd /home/tree/projects/java"
You're doing nothing wrong! You've changed the directory, but only within the subshell that runs the script.
You can run the script in your current process with the "dot" command:
. proj
But I'd prefer Greg's suggestion to use an alias in this simple case.
The cd in your script technically worked as it changed the directory of the shell that ran the script, but that was a separate process forked from your interactive shell.
A Posix-compatible way to solve this problem is to define a shell procedure rather than a shell-invoked command script.
jhome () {
cd /home/tree/projects/java
}
You can just type this in or put it in one of the various shell startup files.
The cd is done within the script's shell. When the script ends, that shell exits, and then you are left in the directory you were. "Source" the script, don't run it. Instead of:
./myscript.sh
do
. ./myscript.sh
(Notice the dot and space before the script name.)
To make a bash script that will cd to a select directory :
Create the script file
#!/bin/sh
# file : /scripts/cdjava
#
cd /home/askgelal/projects/java
Then create an alias in your startup file.
#!/bin/sh
# file /scripts/mastercode.sh
#
alias cdjava='. /scripts/cdjava'
I created a startup file where I dump all my aliases and custom functions.
Then I source this file into my .bashrc to have it set on each boot.
For example, create a master aliases/functions file: /scripts/mastercode.sh
(Put the alias in this file.)
Then at the end of your .bashrc file:
source /scripts/mastercode.sh
Now its easy to cd to your java directory, just type cdjava and you are there.
You can use . to execute a script in the current shell environment:
. script_name
or alternatively, its more readable but shell specific alias source:
source script_name
This avoids the subshell, and allows any variables or builtins (including cd) to affect the current shell instead.
Jeremy Ruten's idea of using a symlink triggered a thought that hasn't crossed any other answer. Use:
CDPATH=:$HOME/projects
The leading colon is important; it means that if there is a directory 'dir' in the current directory, then 'cd dir' will change to that, rather than hopping off somewhere else. With the value set as shown, you can do:
cd java
and, if there is no sub-directory called java in the current directory, then it will take you directly to $HOME/projects/java - no aliases, no scripts, no dubious execs or dot commands.
My $HOME is /Users/jleffler; my $CDPATH is:
:/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
Use exec bash at the end
A bash script operates on its current environment or on that of its
children, but never on its parent environment.
However, this question often gets asked because one wants to be left at a (new) bash prompt in a certain directory after execution of a bash script from within another directory.
If this is the case, simply execute a child bash instance at the end of the script:
#!/usr/bin/env bash
cd /home/tree/projects/java
echo -e '\nHit [Ctrl]+[D] to exit this child shell.'
exec bash
To return to the previous, parental bash instance, use Ctrl+D.
Update
At least with newer versions of bash, the exec on the last line is no longer required. Furthermore, the script could be made to work with whatever preferred shell by using the $SHELL environment variable. This then gives:
#!/usr/bin/env bash
cd desired/directory
echo -e '\nHit [Ctrl]+[D] to exit this child shell.'
$SHELL
I got my code to work by using. <your file name>
./<your file name> dose not work because it doesn't change your directory in the terminal it just changes the directory specific to that script.
Here is my program
#!/bin/bash
echo "Taking you to eclipse's workspace."
cd /Developer/Java/workspace
Here is my terminal
nova:~ Kael$
nova:~ Kael$ . workspace.sh
Taking you to eclipe's workspace.
nova:workspace Kael$
simply run:
cd /home/xxx/yyy && command_you_want
When you fire a shell script, it runs a new instance of that shell (/bin/bash). Thus, your script just fires up a shell, changes the directory and exits. Put another way, cd (and other such commands) within a shell script do not affect nor have access to the shell from which they were launched.
You can do following:
#!/bin/bash
cd /your/project/directory
# start another shell and replacing the current
exec /bin/bash
EDIT: This could be 'dotted' as well, to prevent creation of subsequent shells.
Example:
. ./previous_script (with or without the first line)
On my particular case i needed too many times to change for the same directory.
So on my .bashrc (I use ubuntu) i've added the
1 -
$ nano ~./bashrc
function switchp
{
cd /home/tree/projects/$1
}
2-
$ source ~/.bashrc
3 -
$ switchp java
Directly it will do: cd /home/tree/projects/java
Hope that helps!
It only changes the directory for the script itself, while your current directory stays the same.
You might want to use a symbolic link instead. It allows you to make a "shortcut" to a file or directory, so you'd only have to type something like cd my-project.
You can combine Adam & Greg's alias and dot approaches to make something that can be more dynamic—
alias project=". project"
Now running the project alias will execute the project script in the current shell as opposed to the subshell.
You can combine an alias and a script,
alias proj="cd \`/usr/bin/proj !*\`"
provided that the script echos the destination path. Note that those are backticks surrounding the script name.
For example, your script could be
#!/bin/bash
echo /home/askgelal/projects/java/$1
The advantage with this technique is that the script could take any number of command line parameters and emit different destinations calculated by possibly complex logic.
to navigate directories quicky, there's $CDPATH, cdargs, and ways to generate aliases automatically
http://jackndempsey.blogspot.com/2008/07/cdargs.html
http://muness.blogspot.com/2008/06/lazy-bash-cd-aliaes.html
https://web.archive.org/web/1/http://articles.techrepublic%2ecom%2ecom/5100-10878_11-5827311.html
In your ~/.bash_profile file. add the next function
move_me() {
cd ~/path/to/dest
}
Restart terminal and you can type
move_me
and you will be moved to the destination folder.
You can use the operator && :
cd myDirectory && ls
While sourcing the script you want to run is one solution, you should be aware that this script then can directly modify the environment of your current shell. Also it is not possible to pass arguments anymore.
Another way to do, is to implement your script as a function in bash.
function cdbm() {
cd whereever_you_want_to_go
echo "Arguments to the functions were $1, $2, ..."
}
This technique is used by autojump: http://github.com/joelthelion/autojump/wiki to provide you with learning shell directory bookmarks.
You can create a function like below in your .bash_profile and it will work smoothly.
The following function takes an optional parameter which is a project.
For example, you can just run
cdproj
or
cdproj project_name
Here is the function definition.
cdproj(){
dir=/Users/yourname/projects
if [ "$1" ]; then
cd "${dir}/${1}"
else
cd "${dir}"
fi
}
Dont forget to source your .bash_profile
This should do what you want. Change to the directory of interest (from within the script), and then spawn a new bash shell.
#!/bin/bash
# saved as mov_dir.sh
cd ~/mt/v3/rt_linux-rt-tools/
bash
If you run this, it will take you to the directory of interest and when you exit it it will bring you back to the original place.
root#intel-corei7-64:~# ./mov_dir.sh
root#intel-corei7-64:~/mt/v3/rt_linux-rt-tools# exit
root#intel-corei7-64:~#
This will even take you to back to your original directory when you exit (CTRL+d)
I did the following:
create a file called case
paste the following in the file:
#!/bin/sh
cd /home/"$1"
save it and then:
chmod +x case
I also created an alias in my .bashrc:
alias disk='cd /home/; . case'
now when I type:
case 12345
essentially I am typing:
cd /home/12345
You can type any folder after 'case':
case 12
case 15
case 17
which is like typing:
cd /home/12
cd /home/15
cd /home/17
respectively
In my case the path is much longer - these guys summed it up with the ~ info earlier.
As explained on the other answers, you have changed the directory, but only within the sub-shell that runs the script. this does not impact the parent shell.
One solution is to use bash functions instead of a bash script (sh); by placing your bash script code into a function. That makes the function available as a command and then, this will be executed without a child process and thus any cd command will impact the caller shell.
Bash functions :
One feature of the bash profile is to store custom functions that can be run in the terminal or in bash scripts the same way you run application/commands this also could be used as a shortcut for long commands.
To make your function efficient system widely you will need to copy your function at the end of several files
/home/user/.bashrc
/home/user/.bash_profile
/root/.bashrc
/root/.bash_profile
You can sudo kwrite /home/user/.bashrc /home/user/.bash_profile /root/.bashrc /root/.bash_profile to edit/create those files quickly
Howto :
Copy your bash script code inside a new function at the end of your bash's profile file and restart your terminal, you can then run cdd or whatever the function you wrote.
Script Example
Making shortcut to cd .. with cdd
cdd() {
cd ..
}
ls shortcut
ll() {
ls -l -h
}
ls shortcut
lll() {
ls -l -h -a
}
If you are using fish as your shell, the best solution is to create a function. As an example, given the original question, you could copy the 4 lines below and paste them into your fish command line:
function proj
cd /home/tree/projects/java
end
funcsave proj
This will create the function and save it for use later. If your project changes, just repeat the process using the new path.
If you prefer, you can manually add the function file by doing the following:
nano ~/.config/fish/functions/proj.fish
and enter the text:
function proj
cd /home/tree/projects/java
end
and finally press ctrl+x to exit and y followed by return to save your changes.
(NOTE: the first method of using funcsave creates the proj.fish file for you).
You need no script, only set the correct option and create an environment variable.
shopt -s cdable_vars
in your ~/.bashrc allows to cd to the content of environment variables.
Create such an environment variable:
export myjava="/home/tree/projects/java"
and you can use:
cd myjava
Other alternatives.
Note the discussion How do I set the working directory of the parent process?
It contains some hackish answers, e.g.
https://stackoverflow.com/a/2375174/755804 (changing the parent process directory via gdb, don't do this) and https://stackoverflow.com/a/51985735/755804 (the command tailcd that injects cd dirname to the input stream of the parent process; well, ideally it should be a part of bash rather than a hack)
It is an old question, but I am really surprised I don't see this trick here
Instead of using cd you can use
export PWD=the/path/you/want
No need to create subshells or use aliases.
Note that it is your responsibility to make sure the/path/you/want exists.
I have to work in tcsh, and I know this is not an elegant solution, but for example, if I had to change folders to a path where one word is different, the whole thing can be done in the alias
a alias_name 'set a = `pwd`; set b = `echo $a | replace "Trees" "Tests"` ; cd $b'
If the path is always fixed, the just
a alias_name2 'cd path/you/always/need'
should work
In the line above, the new folder path is set
This combines the answer by Serge with an unrelated answer by David. It changes the directory, and then instead of forcing a bash shell, it launches the user's default shell. It however requires both getent and /etc/passwd to detect the default shell.
#!/usr/bin/env bash
cd desired/directory
USER_SHELL=$(getent passwd <USER> | cut -d : -f 7)
$USER_SHELL
Of course this still has the same deficiency of creating a nested shell.

$0 doesn't work when I source a bash script

I have a simple script test.sh
#!/bin/bash
echo $0
When I run the following from csh terminal:
bash -c 'test.sh'
Then the output is test.sh
But when I run:
bash -c 'source test.sh'
The output is bash
Does anybody know how to print the script name in this case?
#!/bin/bash
declare -r SCRIPT_NAME=$(readlink -f ${BASH_SOURCE[0]})
Use $BASH_SOURCE. From the man-page:
BASH_SOURCE
An array variable whose members are the source filenames where
the corresponding shell function names in the FUNCNAME array
variable are defined. The shell function ${FUNCNAME[$i]} is
defined in the file ${BASH_SOURCE[$i]} and called from
${BASH_SOURCE[$i+1]}.
You can simply refer to $BASH_SOURCE instead of ${BASH_SOURCE[0]} since dereferencing an array variable in bash without an index will give you the first element.
This is a common pattern in my scripts to allow different behavior for sourcing versus executing a script:
foo() {
some cool function
}
if [[ "$BASH_SOURCE" == "$0" ]]; then
# actually run it
foo "$#"
fi
When a script is run using source it runs within the existing shell, any variables created or modified by the script will remain available after the script completes. In contrast if the script is run just as filename, then a separate subshell (with a completely separate set of variables) would be spawned to run the script.
Also you want to modify the shell scripts
This can not work for the same reason that you can not use a child shell to modify the enviroment of the parent shell. The environment of the child process is private and cannot affect the environment of its parent.
The only way to accomplish what you are attempting might be to have make compose a standard output stream that contains a list of shell variable assignments. The standard output could then be used as the input to the parent 'source' or '.' command. Using a 'noisy' program such as make to do this will be significantly challenging.
If, from csh, youre trying to reach session variables defined in a bash shell script, you must call the bash script prior to starting up the csh.
A session gets inherited into subprocesses, like so, where the csh.SESSION and csh.SESSION_INHERITED are merged once the csh 'boots':
bash/
├── csh
│   ├── SESSION
│   └── SESSION_INHERITED
└── SESSION
The bash.SESSION is NOT accessible from within csh shell as this would expose the init runlevel to any subprocesses.. Its is a fork, which during spawn duplicates the environment.
So, you can run from bash: source test.sh; csh. This will expose the exports from test.sh to csh - but its a static export which cannot be modified programatically in the csh.
Haven't 100% understood your question since its very abstract.. But this hopefully helps :)
when you use source or . , you run the script in this Shell. Your script share variables with the Shell, including the $0. When you run the Shell, you typed in 'bash', so the $0 is bash, and the script use it. That's why it shows bashstrong text.
If you want a new $0, use './script_name.sh' instead!

How do I set an alias from a shell script?

I'm trying to set an alias that applies to the current shell (the shell I'm running the script from) from a shell script. The alias is for cd-ing into the folder of the script. Here's the (not working) script:
#!/bin/bash
shopt -s expand_aliases
DIR=$(cd $(dirname "$0"); pwd) # Detect the folder of the script.
alias cdr="cd $DIR" # cd into the folder.
I quickly realized that this didn't work because the alias it made was pertinent to the script's subshell.
Then, I tried to source the file (as in . makeAlias.sh). However, this produced an error: dirname: illegal option -- b.
How do I write a bash script that makes an alias relevant to the outer shell (the shell running the script)?
The immediate problem is that the value of $0 is now -bash. You might want to refactor your code to use a different reference point, or simply hard-code the path.
To answer the "how do I ...?" you aren't doing anything wrong, it's just that the logic has to be adapted to a different environment -- specifically, when you source a script, $0 is that of the parent process, not the name of the script you are sourcing.
Depending on what you are trying to accomplish, maybe this alternative design could work?
newr () { r=$(pwd); }
cdr () { cd "$r"; }
newr
That is, cdr simply changes directory to whatever the variable r contains. The function newr can be used to conveniently set r to your current working directory. You'd define these in your .bashrc or similar, and use them interactively
./makeAlias.sh will be executed in a sub-shell and the changes made apply only the to sub-shell. Once the command terminates, the sub-shell goes and so do the changes.
Sourcing the file using . ./makeAlias.sh or source ./makeAlias.sh will read and execute commands from the file-name argument in the current shell context, that is when a script is run using source it runs within the existing shell, any variables created or modified by the script will remain available after the script completes.

call main script function in script files called inside in shell script

I am still learning to write shell scripting so i don't know whether this can be done.
I have a main script called main.sh
Main.sh
#!/bin/bash
function log {
echo "[${USER}][`date`] - ${*}" >> ${LOG_FILE}
}
home/script/loadFile.sh && home/script/processData.sh
So my question is can i call my log function of main.sh inside loadFile.sh and processData.sh script file ?
I tried it but i got error
line 1: log: command not found
Thanks.
This is not portable, but in bash you can simply export the function definition:
export -f log
home/script/loadFile.sh && home/script/processData.sh
you need to prompt like this:
. home/script/loadFile.sh && . home/script/processData.sh
But if you have an exit command in your loadFile.sh or processData.sh then your main.sh will exist as well
When you start loadFile.sh and processData.sh like you do, they are started as ordinary executables, so parent shell does not recognize then as shell scripts and new instance of shell interpreter is started for each script. New shell interpreter does not know anything about your log function.
When you run loadFile.sh and processData.sh like this:
. home/script/loadFile.sh && . home/script/processData.sh
Shell treats them as shell scripts rather than as ordinary executables and executes in current context, thus making function log visible to them. Also, any functions/variables defined inside loadFile.sh and processData.sh will be visible in parent shell after they will exit, and thus these scripts has many ways to damange parent shell, which makes such way unsafe in some situations.

Execute a bash function upon entering a directory

I'd like to execute a particular bash function when I enter a new directory. Somethink like:
alias cd="cd $# && myfunction"
$# doesn't work there, and adding a backslash doesn't help. I'm also a little worried about messing with cd, and it would be nice if this worked for other commands which changed directory, like pushd and popd.
Any better aliases/commands?
Aliases don't accept parameters. You should use a function. There's no need to execute it automatically every time a prompt is issued.
function cd () { builtin cd "$#" && myfunction; }
The builtin keyword allows you to redefine a Bash builtin without creating a recursion. Quoting the parameter makes it work in case there are spaces in directory names.
The Bash docs say:
For almost every purpose, shell functions are preferred over aliases.
The easiest solution I can come up with is this
myfunction() {
if [ "$PWD" != "$MYOLDPWD" ]; then
MYOLDPWD="$PWD";
# strut yer stuff here..
fi
}
export PROMPT_COMMAND=myfunction
That ought to do it. It'll work with all commands, and will get triggered before the prompt is displayed.
There are a few other versions of this out there, including
smartcd, which I wrote, and has a ton of features including templating and temporary variable saving
ondir, which is smaller and much simpler
Both of these support both bash and zsh
I've written a ZSH script utilizing the callback function chpwd to source project specific ZSH configurations. I'm not sure if it works with Bash, but I think it'll be worth a try. If it doesn't find a script file in the directory you're cd'ing into, it'll check the parent directories until it finds a script to source (or until it reaches /). It also calls a function unmagic when cd'ing out of the directory, which allows you to clean up your environment when leaving a project.
http://github.com/jkramer/home/blob/master/.zsh/func/magic
Example for a "magic" script:
export BASE=$PWD # needed for another script of mine that allows you to cd into the projects base directory by pressing ^b
ctags -R --languages=Perl $PWD # update ctags file when entering the project directory
export PERL5LIB="$BASE/lib"
# function that starts the catalyst server
function srv {
perl $BASE/script/${PROJECT_NAME}_server.pl
}
# clean up
function unmagic {
unfunction src
unset PERL5LIB
}

Resources