15

I have written a bash script which creates a series of directories and clones a project to selected directories.

For that, I need to cd to each directory (project 1 and project 2), but the script doesn't cd to the second directory nor executes the command.

Instead, it stops after cd and cloning in theproject2 directory. Why doesn't it call the cd_project1 function in the following code?

#!/bin/bash
#Get the current user name 

function my_user_name() {        
current_user=$USER
echo " Current user is $current_user"
}

#Creating useful directories

function create_useful_directories() {  
  if [[ ! -d "$scratch" ]]; then
  echo "creating relevant directory"
  mkdir -p /home/"$current_user"/Downloads/scratch/"$current_user"/project1/project2
  else
     echo "scratch directory already exists"
     :
  fi
}

#Going to project2 and cloning 

function cd_project2() {

  cd /home/"$current_user"/Downloads/scratch/"$current_user"/project1/project2 &&
  git clone https://username@bitbucket.org/teamsinspace/documentation-tests.git
  exec bash
}

#Going to project1 directory and cloning 
function cd_project1() {

  cd /home/"$current_user"/Downloads/scratch/"$current_user"/project1/ &&
  git clone https://username@bitbucket.org/teamsinspace/documentation-tests.git
  exec bash

}

#Running the functions  
function main() {

  my_user_name
  create_useful_directories
  cd_project2
  cd_project1    
}
main

Terminal output:

~/Downloads$. ./bash_install_script.sh    
Current user is mihi
creating relevant directory
Cloning into 'documentation-tests'...
remote: Counting objects: 125, done.
remote: Compressing objects: 100% (115/115), done.
remote: Total 125 (delta 59), reused 0 (delta 0)
Receiving objects: 100% (125/125), 33.61 KiB | 362.00 KiB/s, done.
Resolving deltas: 100% (59/59), done.
~/Downloads/scratch/mihi/project1/project2$
Dan
  • 12,494
  • 7
  • 70
  • 94
Jenny
  • 591
  • 1
  • 7
  • 18
  • 3
    Consider accepting one of the answers. If more than one answer is a solution to a question - accept the best one and up-vote another. – LeonidMew Mar 14 '19 at 22:04
  • 1
    Hi LeonidMew. Sorry I have no idea how to accept the answers. Both answers are equally good though. – Jenny Mar 14 '19 at 22:08
  • 1
    At the left of each answer there is a score and up-vote/down-vote button, below buttons is a gray check-mark, click on it. – LeonidMew Mar 14 '19 at 22:13
  • 6
    @Jenny, don't feel rushed. Read [What should I do when someone answers my question?](https://askubuntu.com/help/someone-answers) instead and act accordingly _when you are satisfied_. Just take your time, there is no reason to hurry. It's perfectly OK if you decide in a day or in a week or in whatever time it takes. – PerlDuck Mar 14 '19 at 22:15
  • 2
    @LeonidMew it's barely been 45 minutes since the question was asked, waiting longer is A-OK, a better answer might even come along (like PerlDuck's comment says, it just popped up while I was typing) – Xen2050 Mar 14 '19 at 22:16
  • Hi LeonidMew. I cannot upvote until I have 15 reputation poitns and right now I have only 11 :( and @PerlDuck: Ok! thanks. – Jenny Mar 14 '19 at 22:17
  • 6
    I'm curious what you intended for the `exec bash` to do. – Dennis Williamson Mar 14 '19 at 23:29
  • Why did you think you needed `exec` before `bash`, but not before `git clone` or any other command? – Barmar Mar 15 '19 at 00:59
  • BTW the `If/mkdir` code in create_useful_directory should probably use $scratch or an argument instead of having two ways to construct the expected path – eckes Mar 15 '19 at 10:59
  • You should give everyone _at least_ 24 hours to answer a question before accepting an answer. What you're telling users is counter to what is expected of them. – pipe Mar 15 '19 at 13:05
  • @DennisWilliamson: It was a little misunderstanding when I was writing this script two weeks ago. According to the answer by 'Serge Stroobandt' in [1]: "A bash script operates on its current environment or on that of its children, but never on its parent environment", I thought I have to somehow go inside the current directory to clone something but now I understand that shell scripts actually run inside a subshell. All is good now. :). – Jenny Mar 15 '19 at 19:26
  • [1] https://unix.stackexchange.com/questions/27139/script-to-change-current-directory-cd-pwd – Jenny Mar 15 '19 at 19:36
  • For future reference, the answer that you referenced is wrong. The correct way to have a _script_ change directories for the current environment (its parent) is to source the script (with `source scriptname` or `. scriptname`). As you have found out, this does not apply at all to the circumstances of the question you posted. Specifically, because the functions containing the `cd` commands in your script are executed in the _current_ environment of the script rather than as subshells. – Dennis Williamson Mar 15 '19 at 22:10
  • 1
    @Jenny On the topic of accepted answers, you might consider reading a recent meta post which mentions your question. Specifically, in my answer https://meta.askubuntu.com/a/18494/295286 I've cited the help center's notes that you should not rush in accepting answers, nor feel pressured to do so. Also, as someone who also prefers writing scripts in functions with `main()`, I like your question even more :) – Sergiy Kolodyazhnyy Mar 16 '19 at 02:50
  • @SergiyKolodyazhnyy: OK. Thanks! – Jenny Mar 16 '19 at 08:32

3 Answers3

28

The culprits are your exec bash statements in some of your functions. The exec statement is a bit weird and not easily understood in the first place. It means: execute the following command instead of the currently running command/shell/script from here on. That is: it replaces the current shell script (in your case) with an instance of bash and it never returns.

You can try this out with a shell and issue

exec sleep 5

This will replace your current shell (the bash) with the command sleep 5 and when that command returns (after 5 seconds) your window will close because the shell has been replaced with sleep 5.

Same with your script: If you put exec something into your script, the script gets replaced with something and when that something stops execution, the whole script stops.

Simply dropping the exec bash statements should do.

PerlDuck
  • 13,014
  • 1
  • 36
  • 60
  • 2
    @Jenny Nice to hear. Anecdote: The Perl language also has an `exec` statement with the same behaviour and if you put some statements _after_ an `exec` statement (like `exec something; print "This won't run";`) then Perl will warn you that the `print` statement will never get executed. – PerlDuck Mar 14 '19 at 22:09
  • 9
    BTW congrats on using && after cd, (if you don’t use `set -e`). I have seen Code like `cd tmp; rm -rf *` fail horrible – eckes Mar 15 '19 at 00:07
15

From help exec:

exec: exec [-cl] [-a name] [command [arguments ...]] [redirection ...]
    Replace the shell with the given command.

    Execute COMMAND, replacing this shell with the specified program.
    ARGUMENTS become the arguments to COMMAND.  If COMMAND is not specified,
    any redirections take effect in the current shell.

The key word here is replace - if you exec bash from inside a script, no further script execution can occur.

steeldriver
  • 131,985
  • 21
  • 239
  • 326
0

if you want a return to the directory you started you could use

cd -

But if you are not sure whether a cd command was executed at all it would be better to use the commands for putting working directories onto a stack:

pushd

and return to it (even after multiple directory changes)

popd

be sure to have equaly pushd and popd commands.

  • 2
    I'm not sure you read or understood the problem space. None of these commands will help the user. – pipe Mar 15 '19 at 13:02
  • I nowhere can read the necessity of executing some commands in a shell. So I thought he called the bash to get back into the start directory. If you insist you might say: he asked for the reason of the missing continuation, which is perfectly answered by the other answers. – Bernd Wilke πφ Mar 17 '19 at 09:46