4

This is part of a bash find loop, and I wondered which is more correct syntax and why?

filename="$(echo "$i" | cut -c5-)";
filename=`echo "$i" | cut -c5-`;

Both function for the purpose of getting the file name.

2
  • You might want to look at google-styleguide.googlecode.com/svn/trunk/shell.xml. I changed a few things for my own environment, but it will give an excellent starting point.
    – Walter A
    Commented Aug 1, 2014 at 12:37
  • That's great; it will help my scripts become much more maintainable and help me get rid of some bad habbits.
    – Escher
    Commented Aug 4, 2014 at 9:49

2 Answers 2

5

The Bash manual notes that:

The POSIX $() form of command substitution is implemented (see Command Substitution), and preferred to the Bourne shell’s `` (which is also implemented for backwards compatibility).

So $(...) is preferred over `...` in anything new and Bash-specific you're writing¹. Backtick substitution doesn't nest well (you have to escape them), and has some slightly odd quoting behaviour. You can nest $(... $(...) ...) arbitrarily much without issue. The manual describes the behaviour of the two as:

When the old-style backquote form of substitution is used, backslash retains its literal meaning except when followed by ‘$’, ‘`’, or ‘\’. The first backquote not preceded by a backslash terminates the command substitution. When using the $(command) form, all characters between the parentheses make up the command; none are treated specially.

It's usually considered best practice to quote all string expansions to avoid word-splitting issues, including in places where it isn't strictly required, so "$(...)" is also better practice here.

Finally, two relevant parts from that same wooledge page:

  • It's also time you forgot about `...`. It isn't consistent with the syntax of Expansion and is terribly hard to nest without a dose of painkillers. Use $(...) instead.
  • And for heaven's sake, "Use more quotes!" Protect your strings and parameter expansions from word splitting. Word splitting will eat your babies if you don't quote things properly.

¹Actually, even in POSIX sh scripts, these are considered best practice. There are still weaker shells around, but you'll know if you're going to encounter them.

2
  • In the Bourne-shell family, only the Bourne shell doesn't support `...`. Even the original version of ash (initially a free reimplementation of the Bourne shell in 1989) supported $(...). Commented Aug 1, 2014 at 11:05
  • 1
    Note that it's not only word splitting. It's word splitting and globbing (filename generation). Leaving a command/variable substitution unquoted is the split+glob operator. (that operator only applies in list contexts though, so not in the assignment to a scalar variable). Commented Aug 1, 2014 at 11:07
5

A comment about good-practice in your code not related to `...` vs $(...):

In

filename="$(echo "$i" | cut -c5-)"

There are at least 4 potential caveats (minor unless there's an incentive to exploit them):

  • You can't use echo for arbitrary data as you may get (depending on the echo implementation or environment) problems with values of $i that start with - or contain backslashes. Best is to use printf (here printf '%s\n' "$i") when dealing with arbitrary data.
  • cut and most text filtering utility are not very appropriate for dealing with file paths because they act on every line of their input. So here, you're taking the first 4 characters off every line of $i (newline is as valid a character as any in file names). Best is to use filename=${i#????} (though you may also want to take into account cases where $i contains fewer than 4 characters).
  • command substitution (either `...` or $(...)) removes every trailing newline character from the output of the command. So if $i ends in newline characters, you'll miss them in $filename. ${i#????} also works around that.
  • cut -c5- or ${i#????} removes 4 characters. Beware that in some locales, characters can be made of several bytes. You may want to take that into account. (also note that GNU cut doesn't support multi-byte characters yet (cut -c is the same as cut -b)).

Not the answer you're looking for? Browse other questions tagged .