
On 2013-11-12 11:27, Les Kitchen wrote:
Hi Allan,
I want to check for the existence of a file that can have a variable tail portion of its name. if[-f <file> ] doesn't seem to have the ability to do wild cards. Can anyone suggest how to achieve this?
[I also saw Matthew Cengia's reply. And I agree with him that the answer depends on what you really want to do. But here are just some thoughts of mine based on my guesses about that.]
I'd tend to use something like:
test -n "$(ls -1d prefix* 2>/dev/null)"
No, never parse the output of 'ls'. In this case it's unlikely to be an issue, but best never to do it anway: http://mywiki.wooledge.org/ParsingLs
You can't just do
test -n prefix*
That's what 'test -f prefix*' (or perhaps -e) is for.
because then, if the file doesn't exist, you'll get the glob passed through literally unchanged, and so still get a false positive. There's a bash setting so that non-matching globs become the empty string, but that tends to be more trouble than it's worth.
[...]
You can use 'test -z ...' for the opposite sense.
This only gives you a yes/no test. If you need more than that, you could use something like this code fragment
test $(ls -1d prefix* 2>/dev/null | wc -l) -eq 1
Again, parsing ls is bad; this is worse than your previous example because if, (however unlikely) you have a file with a newline in it, it'll get counted twice. For bash: array=(*); test "${#array[@]}" -eq 1 Or for sh: set -- *; test "$#" -eq 1
to ensure that there is only one match; or even something like
ls -1d prefix* 2>/dev/null >tmp if test $(wc -l tmp) -eq 1; then the_one=$(cat tmp) ... fi
where you could probably do a better job of consing up a good temporary filename, like /tmp/foo$$, and then doing some trap stuff to remove it later.
Or maybe
the_one="$(ls -1d prefix* 2>/dev/null | head -n 1)"
with a subsequent 'test -n "$the_one"'. That is if you're sure there'll only ever be one match or if there are multiple matches you're happy to silently take just the first one.
The thing you have to remember is that glob (wild card) expansion happens quite early, so if you have (say) files foo.c and foo.o then in
test -f foo.*
the foo.* will get expanded and it will be as if you'd written
test -f foo.c foo.o
for which I get "bash: test: too many arguments".
This is true; so to amend my previous suggestion of test -f or test -e: for f in foo.*; do test -f "$f" && do_stuff; break; done Use break to exit after the first match, otherwise remove it.
Sorry, I'm terribly old-fashioned and use 'test' directly, not '[' or even '[[' (which I didn't know about -- thanks, Matthew),
Also worth looking at ((, which is designed specifically for arithmetic and numeric comparisons.
mostly because it just makes the semantics simpler and more regular and more decomposable. 'if' uses the exit status of a command for true or false, and 'test' is just a utility command that can get you an exit status out of various conditions. Maybe it's an old Lisp-ism, but I even tend to write things like
test -e foo.c && echo foo
avoiding the 'if' altogether.
You can also use the 'if' on any command, not just on a 'test'. In fact, it just occurred to me that for a lot of what you might want to do, you could just have:
if ls -1d prefix* &>/dev/null; then ... fi
That is, rely directly on the exit status of the 'ls' command, throwing away all its output, both stdout and stderr.
Less bad, but I'd still try to avoid calling out to an exernal command when a shell loop will do.
In shell programming in general, it's a good idea to pay attention to the exit status of commands.
Agreed.
[...] -- Regards, Matthew Cengia