4
cd folder &>/dev/null ||( mkdir folder && cd folder ) && git clone https://github.com/user/my-project.git

when running the above command I have the problem (I think), that the () spawns a subshell and runs the commands, then when running the git clone I'm not in the expected directory. Is there a way to run this line? My current solution is

cd folder &>/dev/null || mkdir folder && cd folder && git clone https://github.com/user/my-project.git

The thing is, I run cd folder twice even if the directory exists

arco444
  • 19,715
  • 10
  • 57
  • 59
Rodrigo Kondo
  • 43
  • 1
  • 5
  • BTW, `a && b || c` is *not* the same as `if a; then b; else c; fi`. Using boolean logic, you can have `a` succeed, `b` fail, and then `c` succeed (thus causing the whole command to return a successful exit status, despite the unexpected failure); using the `if`, `c` will **only** happen if `a` fails, and not be invoked if `b` fails. Which is to say -- bash doesn't have a ternary operator; pretending it does is a fast route to hard-to-catch bugs. – Charles Duffy May 17 '18 at 15:58
  • @Charles: But surely in this case, OP really meant `{ a || b; } && c`; that is, the desire is to run `git ...` assuming that one of `a` or `b` succeeded. Which is exactly what `a || b && c` does in bash, however confusing that may be to people used to C operator precedence, – rici May 17 '18 at 16:04
  • 1
    Rodrigo: `cd folder` is so cheap that it is not worth doing any work to avoid it. The real problem with this command line is the race condition noted by @CharlesDuffy in a comment to my answer (which I then edited into my answer). – rici May 17 '18 at 16:06

2 Answers2

6

1. How to avoid spawning a subshell

There are two grouping operators in shell:

  • ( commands… ) Runs commands in a subshell
  • { commands… } Runs commands in the current execution environment.

But please be aware that ( and ) are shell meta-characters while { and } are not. The braces are reserved words, but only if they are complete words and appear as the first word in a command. So the braces need to be surrounded by spaces and the closing brace must come after a semi-colon. For a longer explanation, see bash command groups: Why do curly braces require a semicolon?.

Specifically, you would have to write

cd folder &>/dev/null || { mkdir folder && cd folder; } && git clone https://github.com/user/my-project.git

Note the explicit semicolon to terminate the pipeline. You could have used it in the subshell compound command, too, but here you must use it.

However, you shouldn't really use this command, because it could fail if another process happened to create the directory folder after the first cd fails and before the mkdir runs. There is a better way:

2. How to make this command simpler and more robust

Because this is a very common task indeed, mkdir comes with the useful -p option (this option is required by Posix).

mkdir -p some/path

does two things differently:

  1. The intermediate directories are also created, if necessary

  2. No error is produced if the final directory already exists.

So the common idiom for tasks like this is:

mkdir -p folder && cd folder && git clone https://github.com/user/my-project.git

which would work even if folder were a complete path and more than one directory along the path needed to be added.

rici
  • 201,785
  • 23
  • 193
  • 283
1

The () does spawn a sub-shell. Here's an example of a one-line command that might be helpful:

if [ ! -d folder ]; then mkdir folder; fi; cd folder && git clone ...
pcjr
  • 399
  • 2
  • 6
  • This differs from the OP's code insofar as it runs the `git clone` even if the `cd` failed (and thus can clone into the wrong directory if a `folder` already exists but the current user doesn't have `+x` permissions to change into it). – Charles Duffy May 17 '18 at 16:02
  • @CharlesDuffy Good catch, should be fixed now. – pcjr May 17 '18 at 17:29