2

Can anyone tell me why this is always saying that the directory is not writable, when it absolutely is?

    $dnam="/home/bryan/renametest/C D"

    # Is the directory writable
    err=0
    if [ ! -w $dnam ]
    then
        # Not writable. Pop the error and exit.
        echo "Directory $dnam is not writable"
        err=1
    fi
user2021539
  • 949
  • 3
  • 14
  • 31
  • 1
    I see two problems with your code, and can't reproduce the error you're seeing. First problem **$dnam=**, that won't fly. Second problem: you should prefer [[ ]] to [ ]. With the single version I see an "binary operator expected" error. – tink Apr 03 '13 at 06:27
  • oh sorry ... the $dnam= line was just added so I could show what it was. The actual code doesn't have the $. It's actually: dnam=$(dirname $1)\" – user2021539 Apr 03 '13 at 06:32
  • Changing [] to [[]] results in [[: not found – user2021539 Apr 03 '13 at 06:35

3 Answers3

5

You need double-quotes around $dnam -- without them, it's interpreted as two separate shell words, "/home/bryan/renametest/C" and "D", which makes an invalid test expression and hence fails. This should work:

if [ ! -w "$dnam" ]

@tink's suggestion of [[ ]] is a cleaner way of doing tests like this, but is only available in bash (and some other shells with extended syntax). The fact that you get [[: not found means you're using a fairly basic shell, not bash.

Gordon Davisson
  • 118,432
  • 16
  • 123
  • 151
  • ah hah! Ok. Thank you. That took care of that error. Still have another that I've got to work on. – user2021539 Apr 03 '13 at 07:19
  • oops .. no, I spoke too soon. It's still saying it's not writable. – user2021539 Apr 03 '13 at 07:29
  • @Gordon Davisson: but his post is tagged bash? – tink Apr 03 '13 at 17:18
  • @tink: I'm pretty sure the tag is inaccurate. – Gordon Davisson Apr 03 '13 at 19:10
  • @user2021539: From your comment on the question, it looks like you're embedding a double-quote in the value of `$dnam` -- if so, that will cause this (and many other) problems. When the shell parses a command line, it first interprets (and removes) quotes, *then* replaces variables with their values. If the variables' values contain quotes, it's too late for them to have their intended effect, and they just get treated like any other character. In this case, it'll be treating that quote as part of the filename to check for writabaility, and since there's nothing by that name the check fails. – Gordon Davisson Apr 03 '13 at 19:13
0

I see multiple problems:

  • You are using a space inside your variable. This is not illegal, but in combination line you use the variable unescaped and generate the following command:

    if [ ! -w /home/bryan/renametest/C D ]
    

    This is not a valid syntax. The simplest way to fix this is changing the line to

    if [ ! -w "$dnam" ]
    
  • The next problem is worse: On my system, help test returns the text:

    -w FILE        True if the file is writable by you.
    

    Which means, the command doesn't support directories but only files. If you want to check if a directory is writable, you will have to use a different command

Daniel Alder
  • 5,031
  • 2
  • 45
  • 55
0

As everyone else said, the $dnam variable needs double quotes. Here's why:

The [ ... ] is an alias to the test command. If you look in your system, you will see a file called /bin/[ or maybe /bin/usr/[. On some systems, this is a hard link to /bin/test or /bin/usr/test. The if statement executes what comes after the if, and if that command returns a zero exit status, the if statement will execute the then clause. Otherwise, if there is an else clause, that will execute instead.

To allow for boolean testing, Unix included the test command, so you could do this:

if test -d "$directory"
then
    echo "Directory $directory exists!"
fi

Later on, the /bin/[ was added as syntactic sugar. This is identical to the above:

if [ -d "$directory" ]
then
    echo "Directory $directory exists!"
fi

Now, both [ and test are builtin commands, but they are *still commands. This means that the shell interpolates the command and then executes it.

Try executing the following:

$ set -xv    # Turns on shell debugging
$ dnam="/home/bryan/renametest/C D"
dnam="/home/bryan/renametest/C D"
+ dnam='/home/bryan/renametest/C D'
$ test -d $dnam 
test -d $dnam
+ test -d /home/bryan/renametest/C D
$ echo $?
echo $?
+ echo 1
1
$ test -d "$dnam"   # Now with quotes
test -d $dnam
+ test -d "/home/bryan/renametest/C D"
$ echo $?
echo $?
+ echo 0
0
$ set +xv     # Turn off the debuggin

Each command is echoed twice. The first time as written, and the second time after the line is interpolated. As part of the interpolation, the shell splits parameters on white space. As you can see, the test command is testing the presence of /home/bryan/renamtest/C which doesn't exist and thus not writable. I'm actually surprised that the test command didn't print an error message because you passed it an extra parameter.

In the second attempt, you added quotes. These quotes prevented the shell from splitting your parameters on the space and keep the directory name as a single parameter.

Since [ ... ] is a command, you have to take into account the shell's interpolation of variables and other issues. And, if you're not absolutely careful, you can end up with errors.

Even worse, sometimes the [ ... ] might work and sometimes it might not. If your directory name didn't contain spaces, it will work as expected. Imagine you're writing a program, and you test it and everything works because all directories you've tried don't have spaces. Then, someone uses your program, but has a space in the directory. A substantial number of shell script bugs are do to this type of issue in if statements.

This is why Bash introduced the [[ ... ]] tests. The [[ isn't a command but a statement. This means that the shell doesn't directly interpolate the results. Instead, the parameters are parsed, and then any interpolation is done. Thus, this would have worked:

dnam="/home/bryan/renametest/C D"   # No "$" in front of the variable!

# Is the directory writable
if [[ ! -w $dnam ]]      # No quotation marks needed!
then
    # Not writable. Pop the error and exit.
    echo "Directory $dnam is not writable"
    err=1
fi

It's almost always better to use the [[ ... ]] test rather than the [ ... ] test, so go ahead and get into the habit.

One more minor error, you had:

$dnam="/home/bryan/renametest/C D"

This gets interpolated by the shell, so the variable being set is whatever the value of $dnam just happens to be. If $dnam happened to equal "foo", you would been doing this:

foo="/home/bryan/renametest/C D"

Not what you want.

You want to leave the $ off when you set variables:

dnam="/home/bryan/renametest/C D"
David W.
  • 105,218
  • 39
  • 216
  • 337