The shell environment

INPUT · Slides

Guarding with quotes (`'`, `"` and `\`)

01 / 06

Two kinds of quote

Have you ever wondered why there are two kinds of quote? There is a proper difference.

~ $ echo 'a $HOME'a $HOME~ $ echo "a $HOME"a /home/learner

The same thing wrapped, and different results.

How you wrap itWhat the shell does
'...' (single)rewrites nothing; hands it over as seen
"..." (double)rewrites $; keeps spaces safe
unwrappedrewrites $ and *, and splits on spaces

Remember it this way.

single = guards everything (strong)double = guards only the spaces (ordinary)none   = guards nothing (weak)

The view this chapter has held from the start works here too. It is the shell doing the rewriting. Quotes are the signal to that shell saying "hands off".

And quotes never reach the command. The shell strips the marks themselves before handing anything over.

02 / 06

A space was a separator

Where you need quotes most is because of spaces.

~ $ lsmy file.txt~ $ ls my file.txtls: my: No such file or directoryls: file.txt: No such file or directory

One file name, and it was treated as two targets.

ls my file.txt   ^   ^ first  second (split on the space)

When the shell sees a space it thinks "separator here". So a name with a space in it has to be wrapped and guarded.

~ $ ls "my file.txt"my file.txt

There is also a way of guarding a single character.

~ $ ls my\ file.txt

\ is the mark for "treat the next single character with no special meaning". It is called a backslash.

That settles a question you have had since chapter 1.

> why people say it is better not to put spaces in file names

Because you have to wrap them every time. Forget the wrapping and you get quite different behaviour.

03 / 06

Who is expanding the *?

Here is another surprise.

~ $ echo *memo1.txt memo2.txt~ $ echo '*'*

echo is not expanding the *.

echo *  | the shell expands it into file names firstecho memo1.txt memo2.txt

By the time the command receives it, it is already expanded. So the thing interpreting the * in ls *.txt is not ls either; it is the shell.

Not knowing that, you fall into this trap.

~ $ find . -name *.txtfind: unrecognized: memo2.txt

The shell expanded *.txt into memo1.txt memo2.txt first, and find received an argument it could make nothing of.

The right way is this.

~ $ find . -name "*.txt"./memo1.txt./memo2.txt

Wrap it and let find itself decide, because find can interpret * on its own.

ls *.txt              let the shell expand it (this is fine)find -name "*.txt"    let find expand it (wrap it)

Who you want to do the work decides whether you wrap.

04 / 06

Stopping a $ inside doubles

Sometimes you want to stop a $ even inside double quotes. Then you use \.

~ $ echo "price: \$100"price: $100

\$ says "this is just a dollar sign". \ works inside the wrapping too.

The same when you want a double quote inside doubles.

~ $ echo "start \"middle\" end"start "middle" end

Inside singles it is trickier, because \ does not work inside singles either.

~ $ echo 'it\'s'      <- this does not work

You use the trick of closing, guarding outside, and opening again.

~ $ echo 'it'\''s'it's

Read apart, it goes like this.

'it'     it, wrapped in singles\'       a ', guarded outside's'      s, wrapped in singles

The three stick together and become it's. The rule that quotes side by side join up.

There is an easier way out.

~ $ echo "it's"it's

Inside doubles, a single is just a character. Choosing a quote that does not clash is the easiest thing of all.

05 / 06

Always wrap your variables

Here is the most useful manner in this chapter.

> When you use a variable, wrap it as "$name"

Look at why.

~ $ F="my file.txt"~ $ rm $Frm: can't remove 'my': No such file or directoryrm: can't remove 'file.txt': No such file or directory

After replacing the variable, the shell split it on the space once more.

rm $F  | replacerm my file.txt  | split on the spacenow there are two targets

Wrap it and that does not happen.

~ $ rm "$F"

This really does cause accidents in the field.

DIR="/home/user/my documents"rm -r $DIR        <- it tries to remove /home/user/my and documents

If /home/user/my happened to exist, away it goes.

So the watchword is this.

when in doubt, wrap it as "$variable"

Wrapping almost never causes trouble. With a value that has no spaces the result is the same, and when there are spaces it saves you.

06 / 06

Now have a go

Here are the shapes for this lesson.

echo '$HOME'          hand it over as it is (nothing rewritten)echo "$HOME"          replace it with the contentsecho \$HOME           guard a single characterecho "\$100"          stop a $ inside doublesecho 'it'\''s'         put a single inside singlesls "my file.txt"      hand over a name with a spacerm "$F"               wrap your variablesfind . -name "*.txt"  hand the * to find itself

One knack for checking. Look at it first with echo.

~ $ echo rm $F        before really typing it, see what gets handed overrm my file.txt        <- oh, there are two of them!

You can check before removing anything. Putting an echo in front before an operation you cannot undo, like rm or mv, is a move the professionals use too.

Last, here is the order of the decision in one shape.

1. want the contents expanded?  -> yes: doubles / no: singles2. might there be spaces?       -> yes: wrap it in either3. only guarding a symbol?      -> put a \ on the one character

That much undoes nearly all the worry about quotes. Now let us type.