Using grep -q in shell one-liners

2019-06-08 20:04发布

问题:

I've written a script to list commits in a repo that contain a specific file. It's working perfectly, but I don't understand why I had to write this:

for c in $(git rev-list "$rev_list"); do
    git ls-tree --name-only -r "$c" | grep -q "$file"
    if [ $? -eq 0 ]; then
        echo "Saw $file in $c"
    fi
done

Whereas I normally write the same thing like this:

[[ $(git ls-tree --name-only -r "$c" | grep -q "$file") ]] && echo "Saw $file in $c"
# or
[[ ! $(git ls-tree --name-only -r "$c" | grep -q "$file") ]] || echo "Saw $file in $c"

Neither of the short versions work: they don't output anything. When I write it so that it shows all commits that don't contain the file, I do get output:

[[ $(git ls-tree --name-only -r "$c" | grep -q "$file") ]] || echo "Did not see $file in $c"

However, if I then take a commit hash from the output and run

git ls-tree -r <the hash> | grep file

I notice the file is in the tree for some commits, leading me to believe it's just listing all the commits the script processes. Either way, I'm probably missing something, but I can't exactly work out what it is

回答1:

You don't need to wrap the command in a conditional statement ([[ $(command) ]]). In fact, that will never work with grep -q, because you're actually testing whether the command prints anything. You can just do this:

git ls-tree --name-only -r "$c" | grep -q "$file" && echo "Saw $file in $c"

In general, any code block like

foreground_command
if [ $? -eq 0 ]
then
    bar
fi

can be replaced with either

if foreground_command
then
    bar
fi

or even

foreground_command && bar

Which of the three alternatives you should use depends on whether foreground_command, bar, or both are multi-line commands.



回答2:

awk to the rescue:

git ls-tree --name-only -r "$c" | awk "/$file/{printf '%s in %s\n', '$file', '$c'}" 


标签: bash shell grep