4

I'm working on a project to remotely create a repository in my GitLab CE instance (that part is working!) and then to create a directory using the name of the project (working!) and cd into that directory (here's where I have the issue...) and then to initialize locally and add the remote repository (working!!!)

The issue I'm having is solely changing into the new directory. Whether I use the code shown below, or simply try to cd "$1" or cd "$*" I can't seem to get it to work at all!

#!/bin/bash

dir="$*"
wd=$(pwd)
fulldir="$(pwd)/${dir// /\\ }/"
echo "Creating directory $dir"
mkdir -v "$dir"
cd "$dir"
echo "Changing current directory to $dir"
echo $dir
echo $fulldir

The output of this code is:

root@cana:~# ls
glnewproj  test
root@cana:~# bash test Hello World
Creating directory Hello World
mkdir: created directory 'Hello World'
Changing current directory to Hello World
Hello World
/root/Hello\ World/
root@cana:~# ls
Hello World  glnewproj  test
root@cana:~# pwd
/root

How can I cd into my newly created directory? I'm totally stumped.

Edit:

Typed up the function per ghoti and tested it within .bashrc and within my test script.

When running the function directly from bash:

root@cana:~# ls
glnewproj  test  test2
root@cana:~# mkcd "Hello World"
root@cana:~/Hello World# 

When running the function from within a new test script:

root@cana:~# ls
glnewproj  test  test2
root@cana:~# cat test2
#!/bin/bash

mkcd() {
  mkdir -p "$1" && cd "$1"
}

mkcd "$1"

root@cana:~# bash test2 "Hello World"
root@cana:~# ls
Hello World  glnewproj  test  test2

So the script is still running as a child and thus not updating the parent shell's current directory. Is my only option to spawn a new shell at the end of the script?

Community
  • 1
  • 1
Thomas Ramage
  • 73
  • 1
  • 9
  • Your `test2` script is still a separate *script*, and is not running within your interactive shell. The fact that test2 internally has its own function called `mkcd` doesn't change the fact that it's running in a subshell that you created by running `bash` in your interactive shell. Instead of running `bash test2 "Hello World", make test2 a function within your running shell, and run the function. – ghoti Apr 03 '16 at 04:26
  • What I'm trying to do entirely depends on it being a script, however. – Thomas Ramage Apr 03 '16 at 04:35
  • Well, you have multiple answers now, including your own, and you understand why you're having the problem you're having. If you really want to be able to change directory in your interactive shell to something that is created by your script, there are multiple ways of hacking that together, and I've described one in my answer. Whether it's applicable to you is something I can't possibly know given the extent of the detail in your question. – ghoti Apr 03 '16 at 04:39
  • I am lazy if I can be, and I have this in my bash aliases (→here, `cd`'s will remain effective beyond “script” ending): `function mkcd(){` `/usr/bin/mkdir -p "$*"` `builtin cd "$*"` `}`. Thus I can in all lazyness and without **any** quotes go about it and say: `mkcd network testing` – Frank N Oct 03 '21 at 12:05

3 Answers3

6

When you cd from within the script, your directory is changed within the script, but not within the calling shell. The working directory in your interactive shell is set by cd commands in THAT shell, not inside programs that are run by it.

If you want your script to be able to change the directory of your interactive shell, then you'll probably need to set up a bash function. For example, the following could be added to your .bash_profile:

mkcd() {
  mkdir -p "$1" && cd "$1"
}

Since this function runs within the context of your interactive shell, rather than spawning a subshell or child process, the directory changes it does are retained after it exits.


Another option, if you need/want to make your directory creation script a standalone tool, is to model things after mktemp, which creates what it needs to, and then returns the name of what it created to stdout. Thus, with the following script, perhaps called mkcd:

#!/bin/bash

if mkdir -p "$*"; then
  cd -P "$*"
  pwd
  r=0
else
  r=$?
fi

exit $r

Then in your calling script, you could run this like:

#!/bin/bash

d=$(mkcd Hello World)

if [ ! -d "$d" ]; then
  echo "ERROR: I can't go on like this" >&2
  exit 1
fi

cd "$d"
: do your stuff

Note that the error checking remains a good idea even if it seems redundant, as one could fall victim to accidental or intentional unpredictable behaviour if special characters or odd formatting were used to build the directory name.

ghoti
  • 45,319
  • 8
  • 65
  • 104
  • So this should be placed in `.bashrc` yes? And then when run from the script, it should function outside of the script and I can avoid spawning an alarming amount of sessions per my answer. :P – Thomas Ramage Apr 03 '16 at 03:43
  • In your `.bashrc` will work, or your `.bash_profile` if this is for use interactively. For use within a script that you, for example, run from cron, you can just add it to the top of the script. I'm not sure of your use case, we might be making this an [XY problem](http://mywiki.wooledge.org/XyProblem)... – ghoti Apr 03 '16 at 03:44
  • I'm running into some weird behavior with this. I've added it to `.bashrc` and get the desired result if I run the function directly from the shell, but if I run it as a script, the script is still executing it and not affecting the parent shell. – Thomas Ramage Apr 03 '16 at 04:15
  • How are you calling it from within your script? Perhaps you could update your question with these experiments and your results. – ghoti Apr 03 '16 at 04:17
  • Updated the question with some more info. – Thomas Ramage Apr 03 '16 at 04:25
  • Added a comment to your question explaining your results. – ghoti Apr 03 '16 at 04:27
2

With a bit more research, I've discovered that this is because the script is a child process and cannot effect the parent shell. That is to say, the script will cd for its own purposes. I could, for example, change the code to the following:

#!/bin/bash

dir=$1
mkdir -v "$dir"
cd "$dir"
touch test.file

And run

$ bash test "Hello World"

And the result would be the file test.file appearing in ./Hello\ World/, however when the script finishes, I will still be in the previous working directory.

A sloppy way of getting the result I wanted, which is to cd into the new directory, is to spawn a new session within the script using exec bash.

Thomas Ramage
  • 73
  • 1
  • 9
-1

Your script is (mostly) fine, as long as you use cd "$1" as you mentioned in the question. The problem is that you need to make sure the full directory name is actually passed as a single argument.

$ bash test "Hello World"

Then in your script

# Omitting unused $wd
# Omitting $fulldir, which appeared to be for debugging purposes only
dir=$1
mkdir -v "$dir"
cd "$dir"
chepner
  • 497,756
  • 71
  • 530
  • 681