Regex – Regex to Check Arguments of Command

argumentsgrepregular expressionvariable

I want to use grep to check if a command called cmd that was ran, contains the word name as an argument. (I know about about !* in Bash, I don't want to use it).

Let's say I have the command in a variable called process.

At first I tried echo $process | grep 'cmd name'. This isn't refined enough, since it only takes into account one argument.

So I tried to see if I could capture it as the last argument using the regex cmd .*(?= )name. The idea is to capture anything, followed by a space, followed by name (after cmd). But this doesn't work.

My goal, if this worked, was to then try to account for it being an argument in any of the positions.

How can use regular expressions to do this?

Best Answer

In the answer below, I'm explicitly avoiding using grep. A command line is a list of separate items, and it should be parsed as such, not as a line of text.


Assuming that you keep both the command and the arguments of the command in a single string (it would be better to use an array, see How can we run a command stored in a variable?), then you can do something like

process='cmd with "some arguments" and maybe name'

set -f
set -- $process

if [ "$1" != 'cmd' ]; then
    echo 'The command is not "cmd"'
    exit 1
fi

shift

for arg do
    if [ "$arg" = 'name' ]; then
        echo 'Found "name" argument'
        exit
    fi
done

echo 'Did not find "name" argument in command line'
exit 1

This would first disable filename generation, because we want to use $process unquoted to split it up into separate words, and if that string contains filename globbing patterns (like *), it would mess up our parsing of it. We do this with set -f.

Then we set the positional parameters to the words in $process. After that, if "$1" is cmd, we know we should be looking for name in the rest of the command line. If not, we stop there.

We shift off cmd from the list of positional parameters and start looking at the arguments in a loop. In each iteration, we simply compare the argument to the string name. If we find it, we say so and exit.

At the end of the loop, we know we haven't found the name argument, so we report this and exit with a failure.

Note that in my example above, the argument some arguments would be parsed as the two separate strings "some and arguments", which is one of the reasons you'd never want to store a command and its arguments in a single string. It also means that it would detect the argument name inside the single argument "some name string", which would be a false positive.


If the command line is stored in an array (in e.g. bash), then you could do it like this:

process=( cmd with "some arguments" and maybe name )

if [ "${process[0]}" != 'cmd' ]; then
    echo 'The command is not "cmd"'
    exit 1
fi

for arg in "${process[@]:1}"; do
    if [ "$arg" = 'name' ]; then
        echo 'Found "name" argument'
        exit
    fi
done

echo 'Did not find "name" argument in command line'
exit 1

The expansion of "${process[@]:1}" would be the whole array but not the first item (the command name).

For /bin/sh (and dash, but also bash and any other POSIX shell), the above would be less wordy:

set -- cmd with "some arguments" and maybe name

if [ "$1" != 'cmd' ]; then
    echo 'The command is not "cmd"'
    exit 1
fi

shift

for arg do
    if [ "$arg" = 'name' ]; then
        echo 'Found "name" argument'
        exit
    fi
done

echo 'Did not find "name" argument in command line'
exit 1

This is essentially the same as the first piece of code, but with the correct handling of all the arguments (quoting etc.) since we never combine all the elements of the command line into a single text string.

Related Question