1

It is well known that it's a bad idea to do something of the kind <command> $FILENAME, since you can have a file whose name is for example -<option> and then instead of executing <command> with the file -<option> as an argument, <command> will be executed with the option -<option>.

Is there then a general safe way to accomplish this? One hypothesis would be to add -- before the filename, but I'm not sure if that is 100% safe, and there could be a command that doesn't have this option.

Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255

1 Answers1

3

First of all, always enclose your variable between double quotes (there are exceptions to this rule, but you will easily recognize them when the moment comes). The risk that your filename contains space characters is equally high (and probably higher) than a filename that begins with a minus sign.

Your first command should thus be:

<command> "$FILENAME"

Next, the -<option> case. You sum up the issue quite well: using -- is OK but you cannot blindly add -- to your command as all commands do not support this syntax.

A perfectly safe way to protect against variables that contain a filename starting with a minus sign (or any other sensitive character) is to change the filename in order to prepend a path to it, either a relative path (./file) or an absolute path (/foo/bar/file). That way, the first char is harmless since it is either . or /.

This code will add a relative path to $filename unless it is already an absolute path:

[[ "$filename" != /* ]] && filename="./$filename"

Personally, in my shell scripts, I prefer to change file arguments to their canonical representation (full path):

filename="$(readlink -f -- "$filename")"

or:

filename="$(realpath -m -- "$filename")"

(if you wonder which among realpath and readlink you should use, see this excellent answer)

xhienne
  • 17,793
  • 2
  • 53
  • 69
  • Any reason why you prefer to turn file arguments into their canonical representation, other than preference? – Carla is my name Apr 10 '21 at 12:06
  • 2
    Though note that transforming foo.txt to /path/to/foo.txt can make a difference, depending on what you do with the filename in the end. E.g. a symlink containing a full path is different from a relative one (matters if the link is moved or the directory renamed), and tar will store the path as given in the archive, making the extraction also create a path with that particular name. Also, that should be readlink -f -- "$filename" unless of course you both add the initial ./ and then run readlink -f. – ilkkachu Apr 10 '21 at 12:17
  • @AnswerMyQuestion One reason that comes to mind is logs (and script output to terminal): my script logs include the full path and using that path leads immediately to the file (it it exists). If I see, or if someone reports me "file foo not found", the very first question I ask myself is "in what directory was my script running?" (no such question with "file /foo/bar not found") – xhienne Apr 10 '21 at 12:23
  • @ilkkachu You are right. Of course I don't use readlink -f if I want the original symlinks to be preserved. Amending my answer to include -- to readlink, thank you. – xhienne Apr 10 '21 at 12:25