
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)" You can't just do test -n prefix* 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. So, in my suggestion, the glob gets expanded in the arguments to the 'ls' command, whose output is captured by the backticks, $(...) (better behaved than the older `...`). If no matching file exists, then the stdout of 'ls' will be a single empty line, whose trailing newline will get stripped off by the backticks, leaving an empty string for 'test'. The -d option is in case one of the expanded files is a directory, so you just get its name, not its contents. And the -1 option is to force one filename per line. Neither really matters here, but they're often handy for other uses of this idiom. The redirection of stderr doesn't affect the behavior, but just avoids the clutter of the error message from 'ls' when it can't find the file 'prefix*'. And the double quotes are to make sure the -n gets just a single argument even if multiple files match the glob (or if the name has spaces, but I don't want to go there), or if the match is empty. 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 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". Sorry, I'm terribly old-fashioned and use 'test' directly, not '[' or even '[[' (which I didn't know about -- thanks, Matthew), 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. In shell programming in general, it's a good idea to pay attention to the exit status of commands. My only other tip is to take care with your test file-testing options. 'test -f' tests just for a regular file, so will still give false if the thing exists but is a symlink or a directory or a fifo. It depends on what you really want to do. Sometimes 'test -e' (exist) or 'test -r' (readable) are closer to what you need. My above code, with the 'ls' and globbing, is essentially testing just for existence. Often, for practical purposes you need need a combination of test options, like exists and is readable and isn't a directory. Hope this is useful. -- Smiles, Les. P.S. I'm contemplating switching to fastmail -- but that's a topic for luv-talk, I guess. P.P.S. I should note in passing that Perl's behavior with its backticks is different -- newlines don't get special treatment. Same for Ruby. Not sure about Python and other languages, but I suspect they're more Perl-like than shell-like too. Just keep this in mind if ever you want to translate shell code with backticks into a different scripting language.