Bash glob parameter only shows first file instead

2020-02-07 14:30发布

问题:

I want to run this cmd line script

$ script.sh   lib/* ../test_git_thing

I want it to process all the files in the /lib folder.

FILES=$1
for f in $FILES
do
  echo "Processing $f file..."
done

Currently it only prints the first file. If I use $@, it gives me all the files, but also the last param which I don't want. Any thoughts?

回答1:

In bash and ksh you can iterate through all arguments except the last like this:

for f in "${@:1:$#-1}"; do
  echo "$f"
done

In zsh, you can do something similar:

for f in $@[1,${#}-1]; do
  echo "$f"
done

$# is the number of arguments and ${@:start:length} is substring/subsequence notation in bash and ksh, while $@[start,end] is subsequence in zsh. In all cases, the subscript expressions are evaluated as arithmetic expressions, which is why $#-1 works. (In zsh, you need ${#}-1 because $#- is interpreted as "the length of $-".)

In all three shells, you can use the ${x:start:length} syntax with a scalar variable, to extract a substring; in bash and ksh, you can use ${a[@]:start:length} with an array to extract a subsequence of values.



回答2:

As correctly noted, lib/* on the command line is being expanded into all files in lib. To prevent expansion, you have 2 options. (1) quote your input:

$ script.sh 'lib/*' ../test_git_thing

Or (2), turn file globbing off. However, the option set -f will disable pathname expansion within the shell, but it will disable all pathname expansion (setting it within the script doesn't help as expansion is done by the shell before passing arguments to your script). In your case, it is probably better to quote the input or pass the first arguments as a directory name, and add the expansion in the script:

DIR=$1
for f in "$DIR"/*


回答3:

The argument list is being expanded at the command line when you invoke "script.sh lib/*" your script is being called with all the files in lib/ as args. Since you only reference $1 in your script, it's only printing the first file. You need to escape the wildcard on the command line so it's passed to your script to perform the globbing.



回答4:

This answers the question as given, without using non-POSIX features, and without workarounds such as disabling globbing.

You can find the last argument using a loop, and then exclude that when processing the list of files. In this example, $d is the directory name, while $f has the same meaning as in the original answer:

#!/bin/sh
if [ $# != 0 ]
then
        for d in "$@"; do :; done
        if [ -d "$d" ]
        then
                for f in "$@"
                do
                        if [ "x$f" != "x$d" ]
                        then
                                echo "Processing $f file..."
                        fi
                done
        fi
fi

Additionally, it would be a good idea to also test if "$f" is a file, since it is common for shells to pass the wildcard character through the argument list if no match is found.



标签: bash shell glob