-- Leo's gemini proxy

-- Connecting to gmi.noulin.net:1965...

-- Connected

-- Sending request

-- Meta line: 20 text/gemini

History in shell programs


Feed


date: 2024-04-17 07:37:56


categories: linux


firstPublishDate: 2024-04-15 20:34:32


Shell programs save commands on the prompt to history files, the default history file for bash is `~/.bash_history`. I use the history to remember commands I want to reuse.


In bash, I use this configuration in `~/.bashrc`:


# append to the history file, don't overwrite it
shopt -s histappend

# don't put duplicate lines in the history. See bash(1) for more options
export HISTCONTROL=ignoredups

# for setting history length see HISTSIZE and HISTFILESIZE in bash(1)
export HISTSIZE=100000
export HISTFILESIZE=100000
export HISTTIMEFORMAT="%y-%m-%d %T "

export PROMPT_COMMAND="history -a"

The history shows the time when the command was executed and the new lines are added immediatly to the history file. This allows keeping the whole history when multiple shells are opened.


I have an environment (RHEL 7-8+Extra) which ignores $HISTSIZE and $HISTFILESIZE and the lines older than 3 days are deleted.


So I setup my own history which saves the exit code, date and the current path and filters out commands matching a pattern.


The exit code is wrong in the history when a command is suspended, $PROMPT_COMMAND is executed before the command is finished.


export PROMPT_COMMAND='rs=$? ; lastCommand=`history 1 | cut -c 26-` ; ~/bin/saveHistory $rs "$lastCommand"'
bind -x '"\C-r": READLINE_LINE=`fzf --height=20 < ~/myhistory.txt`'
alias h='showMyHistory | less +G'
bind -x '"\C-f": READLINE_LINE=`sed "${READLINE_LINE}q;d" ~/myhistory.txt`'

`rs=$?` saves the exit code to a variable before it is lost

"lastCommand=`history 1 | cut -c 26-`" saves the last command line without the date to a variable. The command line is saved in a variable because the history command doesn't work when it is in saveHistory code. lastCommand could be: 'lastCommand=`HISTTIMEFORMAT= history 1 | sed -e "s/^[ ]*[0-9]*[ ]*//"`'

`~/bin/saveHistory $rs "$lastCommand"` appends exit code, date, pwd and command line to the history files (`~/myhistoryPath.txt`, `~/myhistoryExit.txt`, `~/myhistory.txt`). The date saved in history is when the command ends

I use fzf to search in the history file, invoke with control+r

showMyHistory prints the command history with exit code, date and working directory, the lines are highlighted in red when the exit code is not 0

bind `\C-f` enters a command from the history to the prompt, type command number (line number) in history file and then press control+f. `${READLINE_LINE}` reads the line number from the readline buffer (prompt, it works only in `bind -x`) and sed writes the command from history to the readline buffer


`saveHistory.c` source code, compile with `sheepy -c saveHistory.c` (see below the install instructions):


#! /usr/bin/env sheepy
#include "libsheepyObject.h"
// ARG 1: exit code
// ARG 2: last command
// export PROMPT_COMMAND='rs=$? ; lastCommand=`history 1 | cut -c 26-` ; ~/bin/saveHistory $rs "$lastCommand"'
#define H "~/myhistory.txt"
#define HE "~/myhistoryExit.txt"
#define HP "~/myhistoryPath.txt"
// command is equal to
char *filter[] = {
  "fg",
  "bg",
  "jobs",
  "ls",
  null
};
// command starts with
char *start[] = {
  "fg ",
  "ls ",
  null
};
int main(int ARGC, char** ARGV) {
  cleanCharP(newCmd) = trimS(ARGV[2]);
  // filter out commands
  forEachS(filter, s) {
    if (eqG(newCmd, s)) ret 0;
  }
  forEachS(start, s) {
    if (startsWithG(ARGV[2], s)) ret 0;
  }
  initLibsheepy(ARGV[0]);
  // export PROMPT_COMMAND='rs=$? ; pwd >> ~/myhistoryPath.txt ; d=`date +"%F %T"` ; printf "%s %s\n" "$rs" "$d" >> ~/myhistoryExit.txt ; history 1 | cut -c 26- >> ~/myhistory.txt'
  cleanCharP(hp)  = expandHomeG(H);
  if (isPath(hp)) {
    // ignore duplicate command
    cleanCharP(h) = readFileS(hp);
    size_t len    = lenG(h);
    char *last    = h+len;
    u8 status     = 0;
    range(i, len) {
      if (*last == '\n') {
        if (status == 1) break;
        if (status == 0) {
          // end of last command
          *last = 0;
          inc status;
        }
      }
      dec last;
    }
    // this is a duplicate, stop
    cleanCharP(l) = trimS(last);
    if (eqG(newCmd, l)) ret 0;
  }
  cleanCharP(hep) = expandHomeG(HE);
  cleanCharP(hpp) = expandHomeG(HP);
  char path[8192] = init0Var;
  pError0(appendFileS(hpp, bLGetCwd(path, sizeof(path))));
  pError0(appendFileS(hpp, "\n"));
  char date[128] = init0Var;
  pErrorNULL(bGetCurrentDateYMD(date));
  cleanCharP(edate) = formatS("%s %s\n", ARGV[1], date);
  pError0(appendFileS(hep, edate));
  pError0(appendFileS(hp, newCmd));
  pError0(appendFileS(hp, "\n"));
  // doesnt work - system("history 1 | cut -c 26- >> "H);
  ret 0;
}
// vim: set expandtab ts=2 sw=2:

`showMyHistory.c` source code, compile with `sheepy -c showMyHistory.c` (see below the install instructions):


#! /usr/bin/env sheepy
#include "libsheepyObject.h"
#define H "~/myhistory.txt"
#define HE "~/myhistoryExit.txt"
#define HP "~/myhistoryPath.txt"
int main(int ARGC, char** ARGV) {
  initLibsheepy(ARGV[0]);
  cleanAllocateSmallArray(h);
  cleanAllocateSmallArray(he);
  cleanAllocateSmallArray(hP);
  cleanCharP(hp)  = expandHomeG(H);
  cleanCharP(hep) = expandHomeG(HE);
  cleanCharP(hpp) = expandHomeG(HP);
  readFileG(h, hp);
  readFileG(he, hep);
  readFileG(hP, hpp);
  // find longest path in history
  u16 max = 0;
  iter(hP, E) {
    castS(e,E);
    max = maxV(max, lenG(e));
  }
  u32 count       = lenG(h);
  char counts[32] = init0Var;
  pErrorNULL(bIntToS(counts,count));
  count           = strlen(counts);
  iter(he, e) {
    char *s      = ssGet(e);
    char *date   = findS(s, " ") +1;
    u32 exitCode = parseIntG(s);
    if (exitCode) {
      printf("%*d "RED"%3d"RST" %s "YLW"%*s"RST" "RED"%s"RST"\n", count, iterIndexG(he)+1, exitCode, date, max, getG(hP, rtChar, iterIndexG(he)), getG(h, rtChar, iterIndexG(he)));
    }
    else {
      printf("%*d "GRN"%3d"RST" %s "YLW"%*s"RST" %s\n", count, iterIndexG(he)+1, exitCode, date, max, getG(hP, rtChar, iterIndexG(he)), getG(h, rtChar, iterIndexG(he)));
    }
  }
  ret 0;
}
// vim: set expandtab ts=2 sw=2:

I could save the execution time of each command

I could group commands by shell sessions

When a command in a git repository, git status could be saved in the history (branch name and commit)

I could add hostname and have a common history for multiple machine by sending the command lines to a server.


Sheepy


Sheepy is a build system for using C as a scripting language like python. It takes less than 5 minutes to install sheepy on a machine:


apt-get install gcc git
git clone https://spartatek.se/git/sheepy.git
cd sheepy
sudo -H ./install.sh

The readme has more details.

Sheepy Readme


Feed

-- Response ended

-- Page fetched on Fri May 3 21:21:21 2024