Nov 2023
It’s somewhat complicated to explain the problem so let me just give an example:
Say we want to find the ID of an X window whose name matches an
expression. (This type of problem is easily expressed as an SQL
statement, in case that makes it clearer: SELECT id FROM windows WHERE
name ~ $expr.) And further assume we don’t want to write a program to
do that but instead string together existing tools from the comfort of
our shell.
The wmutils package contains a tool
called lsw that prints out window IDs and the wmutils/opt
package has one called wname that
gives us the name for a given window ID. But how do we put them
together?
The solution I came up with uses xargs and printf to
output the original value next to the result of calling another command
(wname in this case) with that value:
xargswname() {
xargs -I{} sh -c 'printf "%s\t%s\n" "$(wname {})" {}'
}
Now we can grep the output from that function and remove the window names again:
lsw |xargswname |grep 'foo' |cut -f2
And if there was a window with a name that matched foo, we now have
its ID.
To make this command more flexible we obviously want to replace the hard-coded command with a variable. In doing so we need to pay extra attention to quotation because we want the variable to be replaced, but we don’t want the whole subcommand to be executed just yet:
xargscommand() {
xargs -I{} sh -c "printf '%s\t%s\n' \"\$($1 {})\" {}"
}
We can then call this with the command as an argument:
lsw |xargscommand wname |grep 'foo' |cut -f2
Since we want to remove the window name (i.e. the column we filtered on) from the output anyway, we could make that part of the function. We just need to add another argument that contains the grep expression:
xargscommandfilter() {
xargs -I{} sh -c "printf '%s\t%s\n' \"\$($1 {})\" {}" \
|grep ${2:-'.*'} |cut -f2
}
(Note that we set '.*' as the default expression to match everything.)
We also could have made the whole grep command a variable, but I think
the common use case for this function is to grep the results anyway.
Sometimes it is useful to sort values by the output of another command. We can combine it with the previous function by adding a sort command to the pipeline. But we don’t always want to sort, so we probably need to use additional options to our function to control whether and how we want to sort.
Because dealing with different options—we may also want to customize the input value delimiter—gets quite tedious quite fast, I will not go through that step-by-step. You can find the final result of my xargs-command-filter-sort script online.