filename ... dangerous to eval
Sure.
$ touch 'file $(date >&2).txt'
$ bash -c 'eval ls *'
Wed 03 Feb 2021 06:07:08 PM EET
ls: cannot access 'file': No such file or directory
ls: cannot access '.txt': No such file or directory
Don't eval
stuff.
or backtick the output of ls in a bash script?
I'm not exactly sure what this means.
If you mean what you had as an example,
hello=(`ls -t`)
or with a saner syntax,
files=( $(ls -t) )
then you just the get the output of ls
wordsplit to the array.
$ declare -p files
declare -a files=([0]="file" [1]="\$(date" [2]=">&2).txt")
The biggest problem here, even before the possible command injection, is that the space in the filename broke it, we got two array entries instead of one. See the page about parsing ls on Greg's wiki. No, you can't work around it by adding quotes, word splitting doesn't work like that.
So, don't use ls
there. Just let the shell generate the list of filenames:
files=(*)
declare -p files
declare -a files=([0]="file \$(date >&2).txt")
The only problem here, is that Bash doesn't give a good way of sorting files by their date, so ls -t
is tempting. The good alternatives are to put the date in the filename itself so the default sorting gives you sorting by date, or to use Zsh, since it can sort by date. Or do ugly hacks to work around the issue (caveat, I haven't tested that solution).
The same problem with eval
comes if you need to pass the filename to anything that takes just a shell script, like
ssh somehost "do something with $file" # wrong
ssh somehost "do something with '$file'" # still wrong, the name
# could contain single ticks
It seems like the filesystem sanitizes special characters, spaces for instance, by wrapping the filenames in quotes. Will it catch all such things?
Oh dear, oh no it doesn't. If the filesystem did something to prevent storing special characters to the shell, half of the posts on unix.SE wouldn't be needed.
If you want the pain that comes from too much knowledge, here's an essay by David Wheeler about that: Fixing Unix/Linux/POSIX Filenames:
Control Characters (such as Newline), Leading Dashes, and Other Problems
There's also the other one by him, Filenames and Pathnames in Shell: How to do it Correctly. Lots of the subject matter of that has also been discussed here on unix.SE.
Also, quotes don't even help.
$ touch '"quoted name"' 'othername' # two files
$ printf "%s\n" $(ls) # oops
othername
"quoted
name"
$ printf "%s\n" * # this works better
othername
"quoted name"
Because when word splitting happens, quotes are just a regular character. (Unless you set IFS
to include quotes, which probably just makes it worse.) Besides, even if the name is wrapped in quotes, it could still have quotes in the middle, breaking that. You'd need to take care the escape or quote those correctly, too.
It's GNU ls that does the quoting, depending on version and settings:
$ ls -l
total 0
-rw-rw-r-- 1 ilkkachu ilkkachu 0 Feb 3 18:30 othername
-rw-rw-r-- 1 ilkkachu ilkkachu 0 Feb 3 18:30 '"quoted name"'
That's the same as ls --quoting-style=shell
. Incidentally, it seems to get it right, for all of newlines, dollar signs, and quotes. But do you trust it to get it right? If you do, and you know how to use it correctly, you may be able to use it to get the sorted listing.
touch 'file_$(date).txt'; eval $(ls)
in an otherwise empty directory? Your script doesn'teval
it though, it splits and globs the output ofls
and puts it in an array. – muru Feb 03 '21 at 14:42--quoting-style=shell
(orshell-escape
) it seems to get stuff right. That looks to be the default on my Ubuntu 20.04. But plainls -Q
just adds double-quotes, without regard for e.g.$
signs inside. – ilkkachu Feb 03 '21 at 16:00$(...)
syntax, does is called command substitution, it's an expansion the same as variable expansion. Expansions happen at a certain point in the command line processing, and in particular, special operators and quotes that come up as the result of expansions, aren't processed as special (that step has already been done at that point). Whateval
does, is to for another round of all the processing steps, including operators and quotes. – ilkkachu Feb 03 '21 at 17:39foo='"hi there"'; echo $foo
outputs"hi there"
(with quotes, they're not special as the result of the expansion), buteval echo $foo
outputshi there
(without quotes, since the eval removed them). Similarly,foo='echo hi; echo hey'; $foo
printshi; echo hey
(the semicolon isn't special), buteval $foo
would printhi
,hey
on two lines (because now the eval had the semicolon treated as a command terminator, and processed both commands. And so on. – ilkkachu Feb 03 '21 at 17:41eval
with the results, or otherwise shoving the output as part of some shell command, can do that. You get other problems from using$(ls)
, though. – ilkkachu Feb 03 '21 at 17:43$(ls)
in general, see https://unix.stackexchange.com/a/632465/170373 and the links therein. – ilkkachu Feb 03 '21 at 19:34