Pass arguments with a variable
This post is a walk-through of methods I used to learn how to pass arguments stored in a variable to a command.
The focus here is on the process of learning through interactive experimentation, not any actual solutions, though some should be revealed along the way.
I wanted to filter the output of bindkey
by passing these search patterns to grep
:
'\^\[\[' # match "^[["
'\^\[O[A-H]' # match "^[OA"..."^[OH"
'^\"\\\M' # match "\M-^@"-"\M-^?"
'\"-\"~\"' # match " "-"~"
Let’s pretend I actually did things in the following order instead of backtracking several times.
Setup
First, I’ll set up a few things to make it easy to experiment.
Assemble strings in an array
I like the convenience (and readability) of storing strings in an zsh array:
patterns=(
'\^\[\[' # match "^[["
'\^\[O[A-H]' # match "^[OA"..."^[OH"
'^\"\\\M' # match "\M-^@"-"\M-^?"
'\"-\"~\"' # match " "-"~"
)
I’ll also load up some test data into another array:
data=(line1 '^[[' line3) # don't need much
The print
command is my primary tool for both making sure things are as I imagined and, when results are unexpected, figuring out what’s going on:
% print -rl -- $patterns
\^\[\[
\^\[O[A-H]
^\"\\\M
\"-\"~\"
% print -rl -- $data
line1
^[[
line3
Testing with an alias
This alias, assigned to d
, pipes lines from the array I stored test data in:
alias d='print -rl -- $data |'
Now I can easily test commands against my massive, three line data set:
% d grep 'line'
line1
line3
% d grep '\^\[\['
^[[
The advantage of creating an alias on the fly, and letting it die with the terminal window, is I never have a chance to forget it.
An alias won’t exist in the next shell if it’s not saved in a configuration file.
This might seem too obvious to mention, but it took me a long time to appreciate how much freedom that gives me to mess up my current shell without worrying about long-term consequences.
Experiment
Setup complete. Time to experiment.
Pass argument with a variable
I’ll start by trying to pass a single pattern stored in a variable to grep
, using the alias d
(created above) to pipe the test data to the command:
% pat=$patterns[1] # use 1st pattern
% print -r $pat
\^\[\[
% d grep $pat
^[[
That works for a single pattern. But to pass multiple arguments to grep
, I’ll need to prefix each one with “ -e “:
% estring=" -e $pat"
% print -r $estring
-e \^\[\[
% d grep $estring
No output.
Maybe the search argument needs to be surrounded with quotes:
% estring=' -e '\'$pat\'
% print -r $estring
-e '\^\[\['
% d grep $estring
No output.
Skipping past several failed attempts, error messages, internet searches, and further experimentation…
Despite numerous attempts, using various different quoting and escaping techniques, I haven’t managed to successfully pass both an option and argument to grep
in a variable.
According to answers to questions similar to my own, looks like I should try the eval
command:
% estring=' -e '\'$pat\'
% print -r $estring
-e '\^\[\['
% d eval grep $estring
^[[
It worked. Finally!
I’d still like to understand if eval
is really the only solution and, if so, why that’s the case.
Concatenate arguments
Now, instead of passing a single and argument, I’ll try it with multiple arguments.
There are probably other, perhaps even easier, ways to extract and concatenate values from an array, but a simple for
loop should suffice:
for pat in $patterns
do estring=$estring" -e '"$pat"'"
done
Quoting and escaping
You might have noticed different quoting methods being used. Both methods output the same result:
% print -r -- ' -e '\'$pat\'
-e '\^\[\['
% print -r -- " -e '"$pat"'"
-e '\^\[\['
The first method passes four arguments to print
:
1) ` -e `
2) \'
- a single quote, escaped from the shell with a backslash
3) $pat
and
4) \'
- the closing, escaped single quote.
The second method passes three arguments:
1) " -e '"
2) $pat
and
3) "'"
The result looks promising:
% print -r -- $estring
-e '\^\[\[' -e '\^\[O[A-H]' -e '^\"\\\M' -e '\"-\"~\"'
Pass multiple arguments
Now I’ll try using the concatenated arguments with a command:
% d eval grep $estring
^[[
That works.
Finally, I’ll test the command as I actually intend to use it, with the output of bindkey
:
bindkey | eval grep -v $estring
Success.
A working example
The methods shown here are used with grep
and sed
in my bindlist function.