8

Edit: This question was originally bash specific. I'd still rather have a bash solution, but if there's a good way to do this in another shell then that would be useful to know as well!

Okay, top level description of the problem. I would like to be able to add a hook to bash such that, when a user enters, for example $cat foo | sort -n | less, this is intercepted and translated into wrapper 'cat foo | sort -n | less'. I've seen ways to run commands before and after each command (using DEBUG traps or PROMPT_COMMAND or similar), but nothing about how to intercept each command and allow it to be handled by another process. Is there a way to do this?

For an explanation of why I'd like to do this, in case people have other suggestions of ways to approach it:

Tools like script let you log everything you do in a terminal to a log (as, to an extent, does bash history). However, they don't do it very well - script mixes input with output into one big string and gets confused with applications such as vi which take over the screen, history only gives you the raw commands being typed in, and neither of them work well if you have commands being entered into multiple terminals at the same time. What I would like to do is capture much richer information - as an example, the command, the time it executed, the time it completed, the exit status, the first few lines of stdin and stdout. I'd also prefer to send this to a listening daemon somewhere which could happily multiplex multiple terminals. The easy way to do this is to pass the command to another program which can exec a shell to handle the command as a subprocess whilst getting handles to stdin, stdout, exit status etc. One could write a shell to do this, but you'd lose much of the functionality already in bash, which would be annoying.

The motivation for this comes from trying to make sense of exploratory data analysis like procedures after the fact. With richer information like this, it would be possible to generate decent reporting on what happened, squashing multiple invocations of one command into one where the first few gave non-zero exits, asking where files came from by searching for everything that touched the file, etc etc.

Impredicative
  • 5,039
  • 1
  • 17
  • 43
  • This won't be possible without modifying bash itself. Is that within your range of possibilities? Even then, it might prove difficult. Your concept of "command" might not correspond to any internal bash structure. See the odd example in this answer: http://stackoverflow.com/questions/20039771/bash-command-substitution-giving-weird-inconsistent-output/20062738#20062738 – rici Dec 11 '13 at 17:03
  • Modifying bash is not really an option I'd want to consider, sadly! – Impredicative Dec 11 '13 at 17:14
  • Instead of write a shell yourself or modify bash, maybe open to the other shells out there (i think especially of zsh and maybe fish). – Felix Dec 11 '13 at 19:47
  • That's a good point. I'll open this question to other shells. – Impredicative Dec 12 '13 at 09:49
  • This sounds similar to what servers do with requests. For example, Apache does it with varying levels of verbosity, and there are a bunch of modules in Node, Ruby, and other languages. Perhaps you could make a request on every command? Kind of a lot of work and it'll balloon your Internet bill, but if there's no better way... – trysis Jun 21 '15 at 21:45
  • Did you ever come up with a palatable solution? I've been using a homegrown solution similar to the "pass the command to another program which can exec a shell.." route you mentioned. Over time, it got pretty elaborate. It used a common file for assimilation of the results, but I did have plans for a 'listener' (like you described) but never got around to it. Overall, I found this solution very usable, despite the drawbacks of having to protect/escape special characters on the command line. But I still yearn for the same fully automatic solution you are after. – Joe Gibbs Jun 16 '16 at 21:01
  • @JoeGibbs I wrote something with a listener, but I haven't worked on it for a couple of years and it's atrophied somewhat - feel free to take a look: https://github.com/nc6/tabula – Impredicative Jun 20 '16 at 13:54

2 Answers2

2

Run this bash script:

#!/bin/bash
while read -e line
do
    wrapper "$line"
done

In its simplest form, wrapper could consist of eval "$LINE". You mentioned wanting to have timings, so maybe instead have time eval "$line". You wanted to capture exit status, so this should be followed by the line save=$?. And, you wanted to capture the first few lines of stdout, so some redirecting is in order. And so on.

MORE: Jo So suggests that handling for multiple-line bash commands be included. In its simplest form, if eval returns with "syntax error: unexpected end of file", then you want to prompt for another line of input before proceeding. Better yet, to check for proper bash commands, run bash -n <<<"$line" before you do the eval. If bash -n reports the end-of-line error, then prompt for more input to add to `$line'. And so on.

John1024
  • 109,961
  • 14
  • 137
  • 171
  • This doesn't really solve the problem, as complete commands generally don't take exactly one line – Jo So Dec 11 '13 at 23:10
  • @JoSo That is not so difficult. – John1024 Dec 11 '13 at 23:26
  • This is basically a simple version of 'implement your own shell' which I mentioned in the question, and suffers from the same problems: namely, that you lose access to various shell productivity features such as history, tab completion and reverse-i-search. – Impredicative Dec 12 '13 at 09:48
  • To get readline features (tab completion, etc.), just add the `-e` option to `read`. (Bash is good at emulating bash.) If you find a simpler approach to the whole problem than the one I am suggesting, then, certainly, use it instead. – John1024 Dec 12 '13 at 22:36
  • Suggestions: source that file, don't run it as a subshell; embed the wrapper logic directly; use `eval "$line"` – jthill Dec 12 '13 at 22:51
  • @John1024 I can't find a simpler solution (or even a harder one), but adding '-e' doesn't give me any of those things. Do I need to do anything with readline config or similar? – Impredicative Dec 13 '13 at 10:32
  • @John1024 Okay, so sourcing that script seems to give a decent amount of behaviour back. Still not perfect (for example, it exits on C-c), but certainly the best start I have so far! – Impredicative Dec 13 '13 at 10:58
  • @Impredicative Did you ever develop this further? Do you have an example of your final 'script'? Can it handle C-c? – juniper- Nov 24 '14 at 16:27
  • @juniper- Yes, though it's not a script per se - this approach didn't handle enough, so I resorted to using DEBUG traps, PROMPT_COMMAND and so on and building a sessioniser in the background which reconstructs what's going on. The result is https://github.com/nc6/tabula (although I haven't updated it recently). – Impredicative Nov 24 '14 at 17:40
0

Binfmt_misc comes to mind. The Linux kernel has a capability to allow arbitrary executable file formats to be recognized and passed to user application.

You could use this capability to register your wrapper but instead of handling arbitrary executable, it should handle all executable.

alvits
  • 6,550
  • 1
  • 28
  • 28