Bash check if file exists with double bracket test and wildcards

后端 未结 2 731
轮回少年
轮回少年 2020-12-19 00:53

I am writing a Bash script and need to check to see if a file exists that looks like *.$1.*.ext I can do this really easily with POSIX test as [ -f *.$1.*

2条回答
  •  孤城傲影
    2020-12-19 01:29

    Neither [ -f ... ] nor [[ -f ... ]] (nor other file-test operators) are designed to work with patterns (a.k.a. globs, wildcard expressions) - they always interpret their operand as a literal filename.[1]

    A simple trick to test if a pattern (glob) matches exactly one file is to use a helper function:

    existsExactlyOne() { [[ $# -eq 1 && -f $1 ]]; }
    
    if existsExactlyOne *."$1".*.ext; then # ....
    

    If you're just interested in whether there are any matches - i.e., one or more - the function is even simpler:

    exists() { [[ -f $1 ]]; }
    

    If you want to avoid a function, it gets trickier:

    Caveat: This solution does not distinguish between regular files directories, for instance (though that could be fixed.)

    if [[ $(shopt -s nullglob; set -- *."$1".*.ext; echo $#) -eq 1 ]]; then # ...
    
    • The code inside the command substitution ($(...)) does the following:
      • shopt -s nullglob instructs bash to expand the pattern to an empty string, if there are no matches
      • set -- ... assigns the results of the pattern expansion to the positional parameters ($1, $2, ...) of the subshell in which the command substitution runs.
      • echo $# simply echoes the count of positional parameters, which then corresponds to the count of matching files;
    • That echoed number (the command substitution's stdout output) becomes the left-hand side to the -eq operator, which (numerically) compares it to 1.

    Again, if you're just interested in whether there are any matches - i.e., one or more - simply replace -eq with -ge.


    [1]
    As @Etan Reisinger points out in a comment, in the case of the [ ... ] (single-bracket syntax), the shell expands the pattern before the -f operator even sees it (normal command-line parsing rules apply).

    By contrast, different rules apply to bash's [[ ... ]], which is parsed differently, and in this case simply treats the pattern as a literal (i.e., doesn't expand it).

    Either way, it won't work (robustly and predictably) with patterns:

    • With [[ ... ]] it never works: the pattern is always seen as a literal by the file-test operator.
    • With [ ... ] it only works properly if there happens to be exactly ONE match.
      • If there's NO match:
        • The file-test operator sees the pattern as a literal, if nullglob is OFF (the default), or, if nullglob is ON, the conditional always returns true, because it is reduced to -f, which, due to the missing operand, is no longer interpreted as a file test, but as a nonempty string (and a nonempty string evaluates to true)).
      • If there are MULTIPLE matches: the [ ... ] command breaks as a whole, because the pattern then expands to multiple words, whereas file-test operators only take one argument.

提交回复
热议问题