Re: [hackers] [dmenu][RFC][PATCH 0/4] Using sort and simple C program to get dmenu history functionality

From: Xarchus <>
Date: Tue, 1 Dec 2015 05:51:59 -0800

On Mon, Nov 30, 2015 at 03:28:42PM +0100, Silvan Jegen wrote:
> Heyho!
> On Sat, Nov 28, 2015 at 11:25 PM, Hiltjo Posthuma
> <> wrote:
> >
> > This can be implemented in a few lines of shell (wc, sort) and maybe awk.
> I *have* implemented the history part with sort. If you think the
> history updating functionality that I ended up writing in C can be
> (easily?) implemented in some shell script and/or awk then I would
> like to see it on the list :)
> I thought it should be possible to implement in awk but because you
> have to both read input from stdin (the command) and from a file (the
> history file) I couldn't figure it out in the admittedly short time I
> kept trying to do it.
> I tried abusing the -v option of gawk to set the command name as a
> variable value but that only seems to work in a BEGIN block which is
> executed before the input file is read. It may be possible to use the
> shell to read the command and then initialize a variable in the awk
> code given on the command line but I think the argument-escaping hell
> would be very annoying to deal with.

As both Silvan and Hiltjo mentioned, here is an awk script that does the
job of updhist. (more than that, it fixes a problem: the updhist.c as sent
does not work with multisel, it will corrupt the history file if multiple
selections are generated from a single dmenu invocation; but more about
multiselect later)

In order to make the script more portable, I tried to keep the awk features
limited to the nawk set and not using any of the gawk stuff. With gawk,
things can get even simpler: for example gawk has sort, so the external sort
would not be needed in the END block.
(BTW, which is the target as the *official* awk?)

The script assumes the format of the history file to be 'filename tab
count', the same as the C program. The problem with this is what happens
if a file name contains tabs ... Crazy, but not impossible. A more reliable
approach would be to put the count first then the remaining line is all a
file name.

Anyhow, here is the script (tested) :

---------------------%<--------------------- updhist.awk
#!/usr/bin/awk -f

    if(histfile=="") {
        print "updhist.awk: no history file specified" > "/dev/stderr"
        print "usage: awk -v histfile=<path-to-history-file> -f updhist.awk" > "/dev/stderr"
    FS=OFS="\t" # explicit tabs for input to allow file names with spaces
    while ( (getline < histfile) > 0 )
        history[$1]=$2 # assumption: file name does not contain tabs


    for (f in history)
        print f,history[f] | "sort -t '\t' -k2rn >" histfile

To use the above, replace in dmenu_run the call to updhist with 'awk -v
histfile=$historyfile -f updhist.awk' (assuming updhist.awk is placed
somewhere in AWKPATH, or /usr/share/awk, otherwise use the full path to the
awk script).

Because the script will keep the history file already sorted, in
'dmenu_path' there is no need to sort the history when fed to dmenu (so
leave out the 'sort -r -n -t ' ' -k 2').

Silvan talked about the fact that the commands in the history will show up
twice: once from the history, once in the normal list. This too can be
taken care of with a tiny awk one-liner to filter duplicates; that will
replace the 'cat' at the end of the two lines in dmenu_path with this:

awk '!x[$0]++' - "$cache"

Going even further, the cut operation can be factored into awk (I assume it
does cut on the tab that separates the name from the counter), so the whole
line now becomes (this relies on a history format with the name first, tab,
followed by the count):

awk -F$'\t' '!x[$1]++' "$HISTORY" "$cache"

P.S. Now back to multiselect:

This updhist awk script replacement will work with multiselect (multiple
inputs will simply increment their count or added as new). This includes
the case when dmenu outputs duplicate strings (with multiselect, a same
entry can be generated multiple times).

But speaking of multiselect, the dmenu_run as it is now does not handle
very gracefully multiple selections ... Multiple programs selected in dmenu
will all be started, but sequentially, with the next waiting for the
previous to exit (an artifact of them all being fed to one shell instance).
I am not sure if that is the intention: my preference would be to start
each program as soon as it's selected with Ctrl-Return.

For completeness, a full diff with all these changes attached.

Received on Tue Dec 01 2015 - 14:51:59 CET

This archive was generated by hypermail 2.3.0 : Tue Dec 01 2015 - 15:00:13 CET