Search code examples
bashunixstdoutglob

wildcard * not behaving in expected way in bash script


I have a bash script as shown below. I run this in a directory containing files such as input1.inp and other files like coords_i.xyz and submission.sub in order to make some simple modifications to them:

#!/bin/bash 
sed -i -e '25d' *.inp
echo "*xyz -2 2" >> *.inp
sed -n '3,7p' *_i.xyz >> *.inp
echo "Q -1 0 0 3" >> *.inp
echo "Q +1 0 0 -3" >> *.inp
echo "*" >> *.inp
sed -i -e s/"replace1"/"replace2"/g *.sub
rm *.out

If I am in this directory, and I run all the commands individually in the terminal (line by line in the script), everything works fine. However, when I try to group all these commands into the script as shown above, I get an error - essentially after the line sed -i -e '25d' *.inp, the script stops and a file called *.inp is created in my directory. If I try to run the echo command separately after that, it says the command is ambiguous (presumably because of the existence of this *.inp file).

Why don't my wildcards work the same way in the script as they did when I ran them separately and sequentially in the terminal, and what can I do so that they work properly in the script?


Solution

  • Using wildcards this way is hazardous; the easy advice is "don't". Evaluate them only once, and then you can check their outputs before trying to use them.

    In the below, we define an assert_only_one function that stops your script when an array -- assigned from a glob -- contains less or more than exactly one element. Consequently, we're able to write code that more clearly and explicitly describes our desired behavior.

    #!/usr/bin/env bash
    
    shopt -s nullglob      # Stop *.xyz evaluating to '*.xyz' if no such files exist
    
    assert_only_one() {
      local glob; glob=$1; shift
      case $# in
        0) echo "ERROR: No files matching $glob exist" >&2; exit 1;;
        1) return 0;;
        *) echo "ERROR: More than one file matching $glob exists:" >*2
           printf '  %q\n' "$@" >&2
           exit 1;;
      esac
    }
    
    inp_files=( *.inp );   assert_only_one '*.inp' "${inp_files[@]}"
    sub_files=( *.sub );   assert_only_one '*.sub' "${sub_files[@]}"
    xyz_files=( *_i.xyz )
    
    sed -i -e '25d' "${inp_files[0]}"
    {
      echo "*xyz -2 2"
      sed -n '3,7p' "${xyz_files[@]}"
      echo "Q -1 0 0 3"
      echo "Q +1 0 0 -3"
      echo "*"
    } >>"${inp_files[0]}"
    sed -i -e s/"replace1"/"replace2"/g -- "${sub_files[@]}"
    rm -- *.out