3

Running this statement in OS X Terminal

for i in `ls -v *.mkv`; do echo $i; done

will successfully print out all the file names in the directory in name order with each file name on its own line.

Source: This StackOverFlow answer

However, if I run this statement in OS X Terminal

for i in 'ls -v *.mkv'; do echo $i; done

the output is "ls -v fileName1.mkv fileName2.mkv", etc. with all the file names concatenated into one long line (as opposed to each being printed on its own line).

My questions are:

  1. What's the difference between ` and ' in bash?
  2. Why is that difference responsible for the completely different output?
  3. What keyboard combination produces `? (Keyboard combination)
Community
  • 1
  • 1
Ryan D'souza
  • 653
  • 11
  • 22
  • 2
    Don't use `ls`. Just use `for i in *.mkv; do echo "$i"; done` – anubhava Aug 23 '15 at 05:58
  • 1
    'expression', will output the exact string. `expression` execute the content of the expression and echo outputs it. – Zohar81 Aug 23 '15 at 05:59
  • 1
    Take a look at this posting: http://askubuntu.com/a/20041/336375 – Cyrus Aug 23 '15 at 05:59
  • 1
    small example to emphasis my claim : x="ls -ltr". echo '$x' --> $x echo `$x` (show ls -ltr output )--> total 96 -rw-r--r--@ 1 .... – Zohar81 Aug 23 '15 at 06:00
  • 1
    To answer 3: it depends on your keyboard layout, it is different for US, german, french etc. layout. – Gerald Schneider Aug 23 '15 at 06:07
  • See also https://en.wikipedia.org/wiki/Grave_accent#Use_in_programming – tripleee Aug 23 '15 at 06:55
  • 1
    As well as http://stackoverflow.com/questions/10067266/when-to-wrap-quotes-around-a-variable – tripleee Aug 23 '15 at 06:56
  • @anubhava: Duly noted and will do. I only used 'ls -v *.mkv' because it would ensure the files were outputted by name order (lexicographically). – Ryan D'souza Aug 24 '15 at 14:31
  • @tripleee & @GeraldSchneider: The ` character is called a back-tick, and it is located immediately to the left of the '1' on my US keyboard. It is outputted by pressing 'Shift + ~' – Ryan D'souza Aug 24 '15 at 14:34

3 Answers3

7

1) Text between backticks is executed and replaced by the output of the enclosed command, so:

echo `echo 42`

Will expand to:

echo 42

This is called Command Substitution and can also be achieved using the syntax $(command). In your case, the following:

for i in `ls -v *.mkv`; do ...

Is replaced by something like (if your directory contains 3 files named a.mkv, b.mkv and c.mkv):

for i in a.mkv  b.mkv  c.mkv; do ...

Text between quotes or double quotes are just plain Bash strings with characters like space scaped inside them (there are other ways to quote strings in Bash and are described here):

echo "This is just a plain and simple String" 
echo 'and this is another string' 

A difference between using ' and " is that strings enclosed between " can interpolate variables, for example:

variable=42
echo "Your value is $variable"

Or:

variable=42
echo "Your value is ${variable}"

Prints:

Your value is 42

2) Wildcard expressions like *.mkv are replaced by the expanded filenames in a process known as Globbing. Globbing is activated using wildcards in most of the commands without enclosing the expression inside a string:

echo *.mkv

Will print:

a.mkv b.mkv c.mkv

Meanwhile:

echo "*.mkv"

prints:

*.mkv

The i variable in your for loop takes the value "ls -v *.mkv" but the echo command inside the loop body takes $i without quotes, so Bash applied globbing there, you end up with the following:

for i in 'ls -v *.mkv'; do 
   #   echo $i 
   #
   # which is expanded to:
   #   echo ls -v *.mkv    (no quotes) 
   #
   # and the globbing process transform the above into:
   echo ls -v a.mkv b.mkv c.mkv

Which is just a one-line string with the file names after the globbing is applied.


3) It depends on your keyboard layout.

One trick to keep the character around is to use the program ascii, search for the character 96 (Hex 60), copy it and keep it on your clipboard (you can use parcellite or any other clipboard manager that suits your needs).


Update: As suggested by @triplee, you should check useless use of ls as this is considered a bash pitfall and there are better ways to achieve what you're trying to do.

Community
  • 1
  • 1
higuaro
  • 15,730
  • 4
  • 36
  • 43
  • Maybe mention [useless use of `ls`](http://www.iki.fi/era/unix/award.html#ls) as well as http://mywiki.wooledge.org/ParsingLs – tripleee Aug 23 '15 at 06:58
  • I'm not sure what point you are trying to make with the reference to compound commands; no special processing is done. Also, quotes don't create a string; `foo` and `"foo"` both create the same string. What quotes do is implicitly escape *every* character contained in the quotes. `foo\ bar` and `"foo bar"` create identical strings. – chepner Aug 23 '15 at 20:33
  • @chepner thank very much for those nice observations. Updated the string part of the answer. Mind to elaborate more in the compound commands part? I'm trying to come up with an explanation to why the globbing is applied in the string after the `in` and not in other commands, I was sure it was due to the compound command. – higuaro Aug 23 '15 at 21:05
  • 1
    The globbing is applied in the `echo $i` inside the `for`. The list in the `for` is one element. `i` is set to that element (`ls -v *.mkv`). `echo $i` is performed once. It expands to `echo ls -v *.mkv`. The globbing is performed there and a single long string is echoed. – Ken Thomases Aug 23 '15 at 21:36
  • @KenThomases thanks! How could I miss that! Thank you very much! – higuaro Aug 23 '15 at 22:21
1

'expression', will output the exact string in expression.

`expression`, will execute the content of the expression and echo outputs it.

For example:

 x="ls"
 echo "$x" --> $x
 echo `$x` --> file1 file2 ... (the content of your current dir)
tripleee
  • 175,061
  • 34
  • 275
  • 318
Zohar81
  • 4,554
  • 5
  • 29
  • 82
1

Backticks mean "run the thing between the backticks as a command, and then act as if I had typed the output of that command here instead". The single quotes mean, as others have said, just a literal string. So in the first case, what happens is this:

bash runs ls -v *.mkv as a command, which outputs something like:

fileName1.mkv
fileName2.mkv

bash then substitutes this back into where the backtick-surrounded command was, i.e. it effectively makes your for statement into this:

for i in fileName1.mkv fileName2.mkv; do echo $i; done

That has two "tokens": "fileName1.mkv" and "fileName2.mkv", so the loop runs its body (echo $i) twice, once for each:

echo fileName1.mkv
echo fileName2.mkv

By default, the echo command will output a newline after it finishes echoing what you told it to echo, so you'll get the output you expect, of each filename on its own line.

When you use single quotes instead of backticks, however, the stuff in between the single quotes doesn't get evaluated; i.e. bash doesn't see it as a command (or as anything special at all; the single quotes are telling bash, "this text is not special; do not try to evaluate it or do anything to it"). So that means what you're running is this:

for i in 'ls -v *.mkv'; do echo $i; done

Which has only one token, the literal string "ls -v *.mkv", so the loop body runs only once:

echo ls -v *.mkv

...but just before bash runs that echo, it expands the "*.mkv".

I glossed over this above, but when you do something like ls *.mkv, it's not actually ls doing the conversion of *.mkv into a list of all the .mkv filenames; it's bash that does that. ls never sees the *.mkv; by the time ls runs, bash has replaced it with "fileName1.mkv fileName2.mkv ...".

Similarly for echo: before running this line, bash expands the *.mkv, so what actually runs is:

echo ls -v fileName1.mkv fileName2.mkv

which outputs this text:

ls -v fileName1.mkv fileName2.mkv

(* Footnote: there's another thing I've glossed over, and that's spaces in filenames. The output of the ls between the backticks is a list of filenames, one per line. The trouble is, bash sees any whitespace -- both spaces and newlines -- as separators, so if your filenames are:

file 1.mkv
file 2.mkv

your loop will run four times ("file", "1.mkv", "file", "2.mkv"). The other form of the loop that someone mentioned, for i in *.mkv; do ... doesn't have this problem. Why? Because when bash is expanding the "*.mkv", it does a clever thing behind the scenes and treats each filename as a unit, as if you'd said "file 1.mkv" "file 2.mkv" in quotes. It can't do that in the case where you use ls because after it passes the expanded list of filenames to ls, bash has no way of knowing that what came back was a list of those same filenames. ls could have been any command.)

Felix
  • 433
  • 4
  • 8