Nov 2023

It’s somewhat complicated to explain the problem so let me just give an example:

Get window ID by matching window names

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.

Refactor

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.

Adding sorting and finishing up

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.