40

I am new to Linux and Bash scripting. At work, I've seen Bash scripts with constructions similar to this:

mkdir build && cd build && touch blank.txt

Or:

mkdir build; cd build; touch blank.txt

Or even the exotic:

COMMAND="mkdir build && cd build && touch blank.txt"
eval ${COMMAND}

The last example gives one possible use-case where a single line could be useful, but generally the following is easier to read and (at least for me) allows you to visually debug the script:

mkdir build
cd build
touch blank.txt

Are there technical advantages to cramming everything on a single line?

Sergiy Kolodyazhnyy
  • 105,154
  • 20
  • 279
  • 497
AlainD
  • 849

8 Answers8

60
mkdir build && cd build && touch blank.txt

In Bash (and some other shells and most high level programming languages) && is a logical and, which means - if the previous command returns true the next command will be executed. There is also logical or ||. For example you can combine these two options in one statement:

mkdir /tmp/abc/123 && echo 'the dir is created' || echo "the dir isn't created"

Note, the construction cmd_1 && cmd_2 || comd_3 is not a substitute of the if.. then.. else statement, because no matter which of the preceding commands return false, the cmd_3 will be executed. So you must be careful about the circumstances in which you are using it. Here is an example:

$ true && false || echo success
success
$ false && true || echo success
success
$ false && false || echo success
success

As a rule of thumb, usually, when I'm using the cd command within a script, I'm putting a test whether the directory change is successful: cd somewhere/ || exit. More proper test is proposed by @dessert: if ! cd dir; then exit 1; fi. But in all cases as protection of script's failure it is better to use the set -e option as it is shown in the @mrks' answer.


mkdir build; cd build; touch blank.txt

; is a line delimiter and it is used when few separated commands are written in one line.


Note when ; or && and || are in use, it is not mandatory to write the commands at one line - this is illustrated within the @allo's answer.

At all, IMO, there is not any special technical advantage/difference between writing the commands at one or at separate lines.


COMMAND="mkdir build && cd build && touch blank.txt"
eval ${COMMAND}

Here one or more commands (and their arguments) are grouped as value of a variable, and then this variable (argument) is converted to a command by the help of eval. Thus you will be able to change the command that will be executed multiple times within some script by changing it at only one place.

Let's say you need to change the way in which the file blank.txt is created, for example, you can change the relevant line in a way as this:

COMMAND="mkdir build && cd build && echo 'test' > blank.txt"

The actual eval's advantage over the usage of alias is when there are re-directions, pipes, logical operators - at all control flow operators - in use.

In most cases you can use functions instead of eval when alias is not applicable. Also in case the variable contains only a single command, i.e. CMD="cat", we do not need eval, because Bash word-splitting will expand "$CMD" "$file1" "$file2" correctly.

Raffa
  • 32,237
pa4080
  • 29,831
  • 1
    For your diff example, I'd prefer an alias. – pLumo Oct 21 '19 at 13:08
  • 6
    testing cd success is surprisingly easy to skip... yet potentially very disastrous. – Nelson Oct 22 '19 at 02:37
  • @pLumo, for that diff case, one should use a function instead. – ilkkachu Oct 22 '19 at 06:59
  • @pa4080, Actually, that diff case is a really bad example for using eval: it doesn't even need it, and the command breaks if the filenames contain whitespace or almost any special characters, exactly because of the eval. – ilkkachu Oct 22 '19 at 07:04
  • Hello, @ilkkachu, thank you for this note. Actually I can't imagine when I will need to use eval while some filename isn't involved. I've updated the answer. – pa4080 Oct 22 '19 at 08:21
  • @pa4080, yeah, surrounding with single quotes helps... until a filename contains a single quote (probably as an apostrophe, but the basic character is the same). You'd need to change the ''s to '\'' too (and still I'm not sure if that left some hole). – ilkkachu Oct 22 '19 at 08:37
  • 2
    But anyway, my point was that eval is unnecessary here, since you could just use $DIFF "$file1" "$file2", and let the shell word-split $DIFF. It works as long as the command and options don't contain in-word whitespace or special characters, usually they don't. If they do, then a function would be better, or storing the command in an array instead (see here, somewhere in the bottom middle; and/or here). Either would avoid the quoting hell with eval. – ilkkachu Oct 22 '19 at 08:38
  • 2
    A && b || c can execute in strange ways (ie not equivalent to an if). Also might be worth mentioning set -e. – D. Ben Knoble Oct 22 '19 at 14:02
  • Hi, @Nelson, could you give me some additional explanation or source about this case, please. – pa4080 Oct 22 '19 at 14:07
  • Hi, @D.BenKnoble, I've updated the answer. But unfortunately I'm not familiar with set -e, could you point me out a reference? – pa4080 Oct 22 '19 at 21:26
  • 1
    @pa4080 man bash or help set; if a command fails, the shell terminates. Useful for scripts (which run in a subprocess, so they dont terminate the calling shell, unless you use source) – D. Ben Knoble Oct 22 '19 at 21:41
  • @pa4080 fairly simple. cd, then rm * -r -s. If you're in root and tried to cd into an invalid directory, well... uh, yeah... – Nelson Oct 23 '19 at 03:32
  • Hi, @Nelson, I can't reproduce that... let's say we have a script and in certain point there is cd foo/ || exit the script is immediately interrupted if the cd command is not successful... Actually I would not like to have cd in my scripts :) – pa4080 Oct 23 '19 at 10:03
  • Did you answer the question? Is there any technical advantage of a&&b&&c over a&&\nb&&\nc (with \n being actual newlines of course)? You don’t mention that && does not at all require the command lines to be on one line! – dessert Oct 24 '19 at 15:23
  • Hello, @dessert, I will accept your suggestion and will add few additional notes within the next days. BTW, could you help me to understand the statement 'testing cd success is surprisingly easy to skip...' (from the second comment). I'm beating my head about that for the last few days. – pa4080 Oct 24 '19 at 15:59
  • @pa4080 I didn’t get this at first either, but I think it means that a failing cd command in a script can easily make the whole script fail and most people don’t test for it with – in the simplest way – cd dir && …. A proper test like if ! cd dir; then exit 1; fi would be much better though. – dessert Oct 24 '19 at 17:57
13

The answers so far address what happens/how it works but I think you were asking "why"

The main "Reason" to do it (for me) rather than typing them on three lines is that sometimes the commands take time (Sometimes minutes) and you don't want to hang out for the whole time.

For example, I might build && deploy && start my app then head out for lunch. The process could take 15 minutes to complete. However since I used &&, it won't deploy if the build fails--so that works.

Type-ahead is an alternative but it is iffy, you might not be able to see what you type (and therefore make mistakes) or a program might eat the type-ahead (Thanks, grails!). Then you come back from lunch and find out that you still have two longish tasks to kick off because of a typo.

The other alternative is writing a small script or alias. Not a bad idea but doesn't allow as much flexibility, like I can:

stop && build && test && deploy && start 

or I can just:

stop && start

Or any number of other combinations that would quickly pollute a script with bunches of flags.

Even if you do write a script you are likely to integrate it with other scripts with &&.

pa4080
  • 29,831
Bill K
  • 371
10

Combining commands on Linux can be very useful.

A good example may be restarting a remote network interface via ssh (if you've changed network configuration or something...).

ifdown eth0 && ifup eth0

This can prevent you from going physically to the server in order to bring up the interface you where originally ssh'ing to. (You will not be able execute ifup eth0 if ifdown eth0 is executed alone.)

Eliah Kagan
  • 117,780
pymym213
  • 521
  • 4
  • 11
  • 4
    In the particular case ; might be a bit safer as you don’t want to skip ifup in some unknown error cases. – eckes Oct 22 '19 at 01:40
7

What do the commands do ?

In order to understand the why, we also need to understand what is being done. Scripts are sequential. In your last example:

mkdir build
cd build
touch blank.txt

cd build will be executed regardless of mkdir build succeeding or not. Of course if mkdir fails, you will see error from cd.

mkdir build; cd build; touch blank.txt is a sequential list. This can be thought of as essentially the same as multiple script lines. Shell Grammar will treat this slightly differently, but result will be exactly the same as above. Again, commands are executed regardless of whether the previous succeeded.

Finally, there's

mkdir build && cd build && touch blank.txt

which is an AND lists - a sequence of one or more pipelines ( or commands) separated by && operator. Commands in such list are executed with left associativity. This means, the shell will keep taking two commands/pipelines separated by &&, execute one on the left first, and run the one on the right only if left one succeeded ( returned zero exit status ).

In this example, what will happen ?

  • shell executes mkdir build first.
  • above command succeeded ( returned exit code 0 ), cd build will run
  • Again, let's look at the left associativity. ... && touch blank.txt. Did stuff to the left of && succeed ? If yes, run touch blank.txt.

Why use sequential lists vs AND list ?

The sequential list mkdir build; cd build; touch blank.txt makes sense when we are OK with one of the commands failing. Suppose mkdir build already exists. We still want to cd into the build/ directory and touch blank.txt. Of course, the disadvantage here is that there's possibility of unintended result. What if build has no execute bit set, and that's why cd build fails ? The touch blank.txt will happen in our current working directory instead of intended build/.

Now consider mkdir build && cd build && touch blank.txt. This is somewhat more logical, though has its own pitfalls. If build already exists, likely someone already did the touch blank.txt as well, so we may not want to do touch blank.txt again as this will modify the access timestamp on it, which is something that might not be desirable.

Conclusion

Placing commands on the same line depends on the purpose of the commands and what you're trying to achieve. Sequential lists can simplify the output, as shell won't redisplay the prompt until commands have finished. AND list allows for conditional execution of commands and may prevent execution of unnecessary commands. Bottom line is that the user needs to know the differences and what to choose as the right tool for the task.

Sergiy Kolodyazhnyy
  • 105,154
  • 20
  • 279
  • 497
5

Another reason might be in how the script came to be: It is not unusual for technical users to build very long interactive command lines, repeatedly extending and testing them until they work as desired, then pasting the result into a text file and slapping a #!/bin/bash header over it. Sometimes multiple segments of a script are evolved by that method.

Long one line constructs that begin for i in, grep | cut | sed | xargs chains, heavy use of && and ||, $(cat something) constructs are often indicative of such origins.

Eliah Kagan
  • 117,780
4

Why we sometimes like to use it

When we are writing answers here in Ask Ubuntu it is helpful to put them into one line which people can cut and paste into their terminal easier. For example:

$ CurrDir=$PWD ; cd /bin ; ls -la dd ; cd "$CurrDir"

-rwxr-xr-x 1 root root 72632 Mar  2  2017 dd

As mentioned earlier you could use && for the same effect but if there were errors above you could end up on the wrong directory. In this case ; is preferable to && that isn't tested for success:

rick@alien:~/askubuntu$ CurrDir=$PWD && cd /bin && llllssss -la dd && cd "$CurrDir"

llllssss: command not found

rick@alien:/bin$ 

Why we have to use it

There are some commands where you have to string multiple lines together. One case is the abbreviated if-commands-fi which must be one line. For example:

$ [[ 2 -eq 2 ]] && { echo 2 equals 2 ; echo setting three to 3 ; three=3 ; }

2 equals 2
setting three to 3
3

Are there technical advantages to cramming everything on a single line?

It is only a matter of style. There are not technical advantages.

I think others explained what are the differences in the three lines quite well, but note that this does not mean you need to have them on one line.

foo &&
bar

does the same as foo && bar. You still need to be careful, as

foo
&& bar

does not the same, but runs foo and then produces a syntax error. The general rule in most cases: When the shell knows from the syntax, that your line is not complete, it allows you to continue on the next line.
When the first line can be executed as it is, then it will be executed (and the next line may or may not be a syntax error, because the first one is missing).

When you still want your script to be more readable, you can escape the new line character like this

foo \
&& bar

Make sure, that there is no white space after the \ character, as it must come directly before the newline character.

allo
  • 661
2

Many of the answers here talk about safety, e.g., "What happens if cd fails"?

While combining commands such as cd foo && rm * solves that problem, there is a much better way of doing that:

Take this folder structure:

.
./foo
./foo/bar
./foo2
./foo2/bar2
./test.sh
./do_not_delete_me

and the following script:

find .
cd notexisting
rm *
cd ../foo2
rm *
cd ..
find .

As notexisting does not exist, rm * will run in . and delete do_not_delete_me.

However, if you start the script with set -e, it will abort with the first failed command:

./test.sh: line 4: cd: notexisting: No such file or directory

As mentioned in the comments, it makes sense to include it in the shebang: #!/bin/bash -e.

mrks
  • 121