0

I know that variables in bash have no type, but am confused about the value they are assigned.

The following simple script works fine in bash

#!/bin/bash 
tail -n +2 /cygdrive/c/workdir\ \(newco\,\ LLC\)/workfile.txt > \
/cygdrive/c/workdir\ \(newco\,\ LLC\)/workfile2.txt

However, the following does not

#!/bin/bash
tmpdir=/cygdrive/c/workdir\ \(newco\,\ LLC\)
tail -n +2 $tmpdir/workfile.txt > $tmpdir/workfile2.txt

Is there an explanation for this behavior?

2 Answers 2

3

You should make sure to quote variables that may have spaces in them. In your case, tail is receiving three files: /cygdrive/c/workdir, (newco, and LLC) because $tmpdir has three words separated by spaces.

I know you escaped the space when assigning $tmpdir, but that merely served to avoid each word being interpreted as a separate command during assignment. If you then echo $tmpdir, you will get
/cygdrive/c/workdir (newco, LLC), and that's what's being passed to tail.

To avoid this, quote $tmpdir:

tail -n +2 "${tmpdir}/workfile.txt" > "${tmpdir}/workfile2.txt"
2
  • Thanks. What's the difference between "${tmpdir}/workfile.txt" and "$tmpdir/workfile.txt"?
    – gappy
    Jun 5, 2014 at 21:18
  • 2
    Curly braces are used to ensure proper parsing of variable names. If you had a variable called tmp you could do something like ${tmp}dir/workfile.txt to substitute it instead of entire $tmpdir (assuming that one exists too).
    – gronostaj
    Jun 5, 2014 at 21:21
0

Type the first line in bash and then echo that variable, this is what you'll see:

$ tmpdir=/cygdrive/c/workdir\ \(newco\,\ LLC\)
$ echo $tmpdir
/cygdrive/c/workdir (newco, LLC)

If it's not clear yet what's the culprit, add echo at the beginning of your second line and wrap it in double quotes. You'll see what command is actually executed:

$ echo "tail -n +2 $tmpdir/workfile.txt > $tmpdir/workfile2.txt"
tail -n +2 /cygdrive/c/workdir (newco, LLC)/workfile.txt > /cygdrive/c/workdir (newco, LLC)/workfile2.txt

You have properly escaped variable assignment, but that's not enough. Bash performs simple substring substitution, thus inserting additional spaces and breaking your command.

As @savanto suggested in his answer, you can wrap variables in double quotes to ensure that extra spaces aren't treated as argument separators. This line:

tail -n +2 "${tmpdir}/workfile.txt" > "${tmpdir}/workfile2.txt"

will look like this after variable substitution:

tail -n +2 "/cygdrive/c/workdir (newco, LLC)/workfile.txt" > "/cygdrive/c/workdir (newco, LLC)/workfile2.txt"

This is the most common and preferred way of solving that issue. If you desperately want to avoid double quotes, you can try double escaping: first when assigning, then after substitution.

$ tmpdir=/cygdrive/c/workdir\\\ \\\(newco\\\,\\\ LLC\\\)
$ echo $tmpdir
/cygdrive/c/workdir\ \(newco\,\ LLC\)

Quick explaination of triple backslashes: a backslash means: "treat next character as a regular character, ignore any special meaning" (i.e. escape it). First backslash is an escape character, so second one will be treated as a backslash character, not escape character. Thus, double backslash produces a single backslash. Third one simply escapes following character, like space or parenthesis. For example a\\\ b will become a\ b.

Now if we substitute variables in your command, it will be escaped properly:

tail -n +2 /cygdrive/c/workdir\ \(newco\,\ LLC\)/workfile.txt > /cygdrive/c/workdir\ \(newco\,\ LLC\)/workfile2.txt

You must log in to answer this question.

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