0

If I run a code like this:

./script *.txt

*.txt will be expanded to all files with .txt extension. But if there is no such file, script will be called with *.txt string. I wonder if there is a way to force expansion to "" when there is no such file. So it means if there is no such file, the script will be called without any argument.

Any idea?

Afshin
  • 103
  • 3
  • 3
    Bash has a `nullglob` shell option (set with `shopt -s nullglob`) - see [Why is nullglob not default?](https://unix.stackexchange.com/questions/204803/why-is-nullglob-not-default) – steeldriver Nov 24 '22 at 21:43
  • 1
    Note that `./script ''` / `./script ""` is not the same as `./script`. The latter will call script with no argument, while the former will call it with one empty argument. – Stéphane Chazelas Nov 24 '22 at 21:47
  • See also `./script *.txt(N)` in zsh (where the nullglob option also comes from). Do you have to use `bash`? – Stéphane Chazelas Nov 24 '22 at 21:48
  • @StéphaneChazelas I have access to bash and sh (the one that is called by default in makefiles). – Afshin Nov 24 '22 at 21:50
  • 2
    Is there something stopping your from letting the script test with `[ -e "$1" ]` if the first argument exists in the filesystem, and if it does, continue processing as usual? – Kusalananda Nov 24 '22 at 22:21
  • @Kusalananda at the end, I used this method. Thanks for the help though. – Afshin Nov 25 '22 at 02:32

1 Answers1

1

There are a few ways you can do this, depending on what is convenient and possible.

  • If you can't change the script but it will handle getting no arguments in a graceful manner, then set the nullglob shell option using shopt -s nullglob in the shell which performs the filename globbing operation and calls the script. This will cause the pattern to be removed completely if it does not match. (Note that the pattern will not be replaced by an empty string, as an empty string still counts as one argument.)

  • If you can't change the script and if it can't handle getting no arguments gracefully, then set the failglob shell option using shopt -s failglob in the shell which performs the filename globbing operation and calls the script. This will cause the shell to emit an error if the pattern does not match, and the script will not be called at all.

  • If you can change the script, then make it test its first argument with [ -e "$1" ] to see whether it exists in the filesystem. If it exists, you know that the filename globbing pattern has matched something. You may then continue to process as usual. Otherwise, you may assume that the pattern did not match anything (or that the script was called with no arguments, or that the pattern matched a broken symbolic link (and possibly other things)) and take the necessary actions for this scenario.

    #!/bin/bash
    
    if ! [ -e "$1" ]; then
        # no files given
        exit 1
    fi
    
    # continue processing names from "$@"...
    
  • Similarly to the previous point, but doing the test before calling the script:

    set -- *.txt
    [ -e "$1" ] && ./script "$@"
    
Kusalananda
  • 320,670
  • 36
  • 633
  • 936
  • That's misleading `[ -e "$1" ]` doesn't check whether files were given, but whether the first argument is an accessible file (after symlink resolution). Even if you change it to `[ ! -e "$1" ] && [ ! -L "$1" ]`, considering that `*.txt` expansion is done against what is returned by `readdir()` and doesn't check whether the files are accessible, so there'll be corner cases where `*.txt` expands to files some of which are not accessible, not to mention the race condition when the file disappears in between the glob expansion and `[` is run. – Stéphane Chazelas Nov 25 '22 at 09:21
  • @StéphaneChazelas The main focus of the question is to handle the case where the filename glob is _not_ matching anything. If the glob does not match anything, the first argument to the script will not correspond to an existing name (it will be the unexpanded pattern). I don't think we can do much to prevent race conditions, but if the pattern expands to unaccessible names, then this falls outside the scope of the question (EDIT: actually, it may not, but I'm uncertain how to fix it without making it super convoluted and difficult to understand). – Kusalananda Nov 25 '22 at 10:48
  • Note that in bash failglob in scripts works like at the prompt of an interactive shell. It doesn't exit the shell, it exits the subshell if any and otherwise returns to the would be prompt if the shell was interactive (the next command to read). For instance. `printf '%s\n' 'echo /+*' 'echo 1' '{' ' echo /+*' ' echo 2' '}' 'echo 3' '(/bin/echo /+*); echo 4' | bash -O failglob` still outputs 1, 3 and 4. Also note that failglob takes precedence over nullglob, another reason why it's hardly usable. Not to mention that globbing applies to unquoted expansions there! – Stéphane Chazelas Nov 25 '22 at 10:56
  • @StéphaneChazelas Thanks, that was a misconception on my part, because I was testing with `bash -O failglob -c 'echo *boo*; echo ok'` (`ok` is not outputted). – Kusalananda Nov 25 '22 at 11:58