5

I'd like to be able to keep statistics on which programs I run the most through bash. Is there any way to insert some sort of hook into bash so that it logs everything it execs?

Note that looking through the bash history file isn't good enough. I can print out the first token from each line of the history, but that doesn't get pipelines and process substitution. I'd like the following:

$ ls /usr/bin/* | fgrep $(echo grep)

to log that ls, fgrep, and echo were each run once. And the following:

$ for file in /usr/bin/*; do stat $file; echo $file; done

should note that stat and echo were run. Since for is a bash flow control statement, it wouldn't be logged.

It's debatable whether shell built-ins and functions should be logged (maybe just logged with a note about what they were). It's also debatable whether stat and echo should be logged once or however many times the loop runs in the above example. But I could work with the output whichever way was easiest.

Running bash with -x shows that it does have some internal mechanism to hook into the exec process and log what's happening. Is there any other way to get in there?

4
  • 1
    If you don't mind seeing every process run by you, including those invoked by other processes (say, if you run a command from within vim or from the Applications menu on your desktop), an easy way to do this is to install process accounting (on Ubuntu, this is the acct package). Then you can run sudo lastcomm myusername. Feb 24, 2015 at 16:41
  • Why don't you use the history? Feb 24, 2015 at 16:41
  • I explained why I don't just use the history. I'd still have to do all the parsing for each line to find out each processed exec'd
    – onlynone
    Feb 24, 2015 at 17:56
  • @MarkPlotnick that's actually pretty interesting. Not exactly what I was looking for, but pretty close. I kind of wanted to know the commands I ran the most, this will also show stuff like groff, grotty, and troff when I run man.
    – onlynone
    Feb 24, 2015 at 18:06

3 Answers 3

3

As a first cut at it,

strace -e execve -b execve -f -qqv -e signal='!all' bash 

for non-smoketest use you'd have to redirect stderr to some logger pipe, or litter some poor directory somewhere with files using strace's -o, maybe -o ~/commandtraces/$(date +%Y%m%d%H%M%S).$$

To do it for all interactive shells I'm thinking you'd need a guard variable in your .bashrc to avoid recursion.

3
  • Not bad. But I think you'd want follow to be off (no -f flag). Otherwise, you'd run into the same issues as using the acct package. Every exec, not just those done by bash directly would be recorded.
    – onlynone
    Feb 26, 2015 at 17:15
  • 1
    -b execve stops following on exec. Try it.
    – jthill
    Feb 26, 2015 at 17:22
  • Unfortunately I can't test it out just now (on a mac without strace) but I'm going to mark this as the answer since it looks like most likely comes very close to (if not exactly) what I wanted.
    – onlynone
    Mar 2, 2015 at 16:51
0

When it is your local system, and you are very carefully, you can replace all /usr/bin files by wrappers.
Something like

cp -pr /usr/bin /usr/orgbin
cd /usr/bin
for f in *; do
   echo "/usr/orgbin/echo $f used >> /tmp/bin_usage.out" > $f
   chmod +x $f
done

First try with 2 innocent programs?

1
  • That is a very bad idea and I would recommend that no one try that. And you'd still just be getting what @MarkPlotnick 's solution gets: ie not just programs run from bash, but all exec'd programs. And you'd have to do it for each path in your PATH. And every user would be trying to write to the same log file.
    – onlynone
    Feb 25, 2015 at 21:25
-1

You can use the PROMPT_COMMAND hook to insert something that will log the last command typed. That log can be made with the help of the logger command.

Something like export PROMPT_COMMAND="logger `history 1`"

3
  • As I said, that won't work since the commands logged will just be the entire command line string including bash built-ins, flow control statements, pipelines, etc. This is why I can't just use the history directly.
    – onlynone
    Feb 24, 2015 at 17:55
  • Use some filtering... Feb 24, 2015 at 19:20
  • I'd have to do way more than filtering. I'd have to do full bash code parsing, which is not trivial. Consider just the following ls /usr/bin/* | fgrep $(echo grep) | egrep `printf blah && true`. It would need to find exactly ls, fgrep, echo, egrep, printf, true, and nothing else.
    – onlynone
    Feb 24, 2015 at 22:03

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .