0

I want to extract all the commands/programs in ~/.bash_history and count how many times each one was called. The arguments to the programs are irrelevant and should be ignored. I used this bash oneliner to do that:

cut -f 1 -d ' ' ~/.bash_history | sort | uniq -c | sort -h

But there are a few that are miss this way. For an example, in the following lines the commands mount, tee, mktemp and cut should be counted:

sudo mount /dev/sdb2 /foo/bar
candump can0 | tee canlog
T=$(mktemp -d)
diff <(cut -f 2 -d ' ' ./foo ) ./bar

But they aren't. How can be counted how many times each command is called? Is there are reasonable way?

I do not care if mandatory builtins, like exit, are counted, but other builtins, like echo, that also exist as a normal program, should be counted, aliases should be counted as well. There are no function and no if/else branches, it does not matter if loop keywords and/or commands on the same line as loops are counted, because there are only a tiny amount and the numbers does not need to be exact.

I want to do that so i know how many times each command is called so i can us the shortest aliases for the most used commands.

Edit

For this example ~/.bash_history file:

git commit
sudo mount /dev/sdb2 /foo/bar
gcc -Wall -o foo ./bar.c
git add bar.c
T=$(mktemp -d)
diff <(cut -f 2 -d ' ' ./foo )  <(cut -f 2 -d ' ' ./bar )
diff <(cut -f 2 -d ' ' ./foo1 )  <(cut -f 2 -d ' ' ./bar )
diff <(cut -f 2 -d ' ' ./foo2 )  <(cut -f 2 -d ' ' ./bar )
Vd <(cut -f 2 -d ' ' ./foo2 )  <(cut -f 2 -d ' ' ./bar )
T2=$(mktemp -d)

I expect an output like this:

1  sudo
1  gcc
1  Vd
1  mount
2  mktmp
2  git
3  diff
8  cut 

But i get this:

1  sudo
1  gcc
1  Vd
1  T=$(mktemp
1  T2=$(mktemp
2  git
3  diff

In this case Vd is an alias for vimdiff.

Edit 2

I came up with a script that is probably more accurate but still has some problems. I now go through every possible program and alias and count how many times it occurs in ~/bash_history. But there are still some problems. If an argument to any other program matches that the name of a program or alias, it will be counted but it shouldn't.

For the example above i get this output:

<many programs with count 0 that i removed, they don't matter>
1 gcc
1 mount
1 sudo
1 Vd
2 c
2 git
2 mktemp
3 diff
8 cut

The alias c is listed because it matches the c in bar.c. But other than that the output is as it should be.

Here is the script i made:

#!/bin/bash

#Exit script when a program encounters an error or a variable is used which was not defined
set -eu

#Use ~/.bash_history if no argument is given, otherwise use $1
INPUT="~/.bash_history"
if [ $# -ge 1 ]
then
  INPUT="$1"
fi



#Store the code to count the occurences of a program in a input file in the variable FUNCTIONCODE
#Do this because we can not call a function from find, so we have to store the function code and 
# to find, to work around this limitation.
#The code must be called with "$FUNCTIONCODE <input file> <programToCount>" to a shell interpreter
#
#The code counts how many times a program occurs in the given input file and prints the count + word.
#
#The -d argument means read till the end. But then read returns a non-true value, so the || true
# at the end makes bash ignoring the non-true value
read -r -d '' FUNCTIONCODE << '.EOT'   || true
#Needs the input file as $1
#Need a function as argument at $2
count()
{
  #Get the program name in case we became the complete path
  program="$(basename $2)"
  #grep -w will only search for full words. -F will ignore any regex and -o will print only the
  # matching word. We can't use -c because that will only count one per line, even when there are
  # multiple matches on a single line.
  printf "%5i %s\n" "$(grep -o -w -F "$program" "$1" | wc -l)" "$program"
}
#Call the function. Since this FUNCTIONCODE string will be combined with the program argument, the
# program is given to the function count as an argument
count 
.EOT


#Print how many times each function is called
PrintAll()
{
  #Check every program in every PATH location
  for DIR in $(echo "$PATH" | tr ":" "\n")
  do
    find "$DIR/" -type f,l -executable -exec bash -c "$FUNCTIONCODE $INPUT {}" \;
  done
  #Check every alias in ~/bashrc . We assume all aliases start at the beginning of a line.
  for ALIAS in $(grep '^alias' ~/.bashrc | cut -d ' ' -f 2 | cut -d '=' -f 1)
  do
    bash -c "$FUNCTIONCODE $INPUT $ALIAS"
  done
  #Check every alias in ~/.alias
  for ALIAS in $(grep '^alias' ~/.alias | cut -d ' ' -f 2 | cut -d '=' -f 1)
  do
    bash -c "$FUNCTIONCODE $INPUT $ALIAS"
  done
}
#Use uniq because some programs are listed twice, this happens for exampel when an alias matches the
# name of a program.

PrintAll | sort -h | uniq
2

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.