
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? TIA.

On 2013-11-11 23:31, Allan Duncan wrote:
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?
You've not quite given enough information, so I'll take a couple of guesses: If you know you will only have one file that matches, then you *can* use test ([) because it'll expand the pattern generated by the wildcard: mattcen@owen:~$ ls -l total 0 mattcen@owen:~$ touch file.foo mattcen@owen:~$ if [ -f file.* ]; then echo exists; else echo doesn\'t exist; fi exists Bash supports [[ as well as [. [[ is more powerful, more lenient to quoting, and in general, recommended as long as you can depend on using Bash rather than being POSIX compliant. Run 'help [[' for more information. It also supports regexesm but you probably don't need them for your use-case. -- Regards, Matthew Cengia

bash4$ with-temp-dir with-temp-dir: entering directory `/tmp/with-temp-dir.Y8IIFS' This directory will be deleted when you exit. bash4$ touch 1.jpg 2.png 3.png bash4$ shopt -s nullglob bash4$ gifs=(*.gif) jpgs=(*.jpg) pngs=(*.png) bash4$ echo "${#gifs[@]}" "${#jpgs[@]}" "${#pngs[@]}" 0 1 2 bash4$ ((${#gifs[@]})) || echo fail fail bash4$ ((${#jpgs[@]})) || echo fail bash4$ ((${#pngs[@]})) || echo fail bash4$

PS: the traditional (SUS portbale) way is if [ '*.jpg' != "$(echo *.jpg) ] then ... fi

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.

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

On 11/11/13 23:31, Allan Duncan wrote:
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?
Thanks for the suggestions. I realised looking at these that my error was putting " around the file name. Lose them and it works fine. As an aside, the need is to kill a "log" file XFCE creates _sometimes_ on shut down that causes various parts of the desktop to not appear on next boot, along the lines of .cache/sessions/xfce4-session-zeus:0 It only happens on my second machine, and while inconvenient it is not a show stopper. The trouble is my wife's machine's system partition was a clone of this one...
participants (4)
-
Allan Duncan
-
Les Kitchen
-
Matthew Cengia
-
trentbuck@gmail.com