Search code examples
shellzshrc

capture the command that launched the running process in my shell


I try to make the title of my windows in GNU screen automatically equal to the path of the working directory PLUS the process running if there is one (e.g: npm start or vim file.js)

for that purpose I added these lines in my .zshrc :

precmd () {
  local action = action_to_define
  if [[ $TERM == screen* ]]; then
    printf -Pn '\ek%~ $action\e\\'
  fi
}

this send (somehow) the path as a title to screen (see this post)

and the variable action would print the running program if it exist

I tried local action= $(history | tail -1 | sed 's#[0-9 ]*##') because this select the prompt of the last command in the history (just like history !! would do if the option !! was recognized, which is not for some reason...)

and local action= $(ps -lr | tail -1 | sed 's#^.*:...##') because this select the command of the running process

but it doesn't works, as if the process was not captured neither by history or ps... maybe precmd run before the action is launched, so I tried other functions like preexec or zshaddhistory without any luck...


Solution

  • The precmd hook is only run after a command finished, just before the next prompt is displayed. So it can be used to change the terminal title for when the prompt is shown. In order to change the terminal title when a command is run, you need the preexec hook, which is run after a command was accepted (after hitting Enter, just before the command is run.

    When confirming a command on the prompt, this command is then passed to preexec as arguments in three forms

    1. The first argument is the string that was typed, including any new-lines (As long as the history mechanism is active, which it usually is)
    2. The second argument is a single line, size-limited (it is cut off, if it gets too long) version of the command with expanded aliases.
    3. The third argument is the full text of the command that is actually run (with expanded aliases)

    So this should do the trick:

    preexec () {
      local action="$1"
      if [[ $TERM == screen* ]]; then
        printf -Pn '\ek%~ $action\e\\'
      fi
    }
    

    As precmd hook is only run, before prompting the next command, it can be simplified:

    precmd () {
      if [[ $TERM == screen* ]]; then
        printf -Pn '\ek%~\e\\'
      fi
    }
    

    Although it is not the main reason why it would not work, it is important to note that in Zsh (and most other Unix shells) there must be no spaces before or after = when assigning values to parameters.

    The exact behavior might differ, depending on where you put spaces and whether you use something like local or set or just plain assignments.

    To use the code from the question as example:

    • putting spaces before and after = with local will usually result in a bad assignment

      % local action = action_to_define
      zsh: bad assignment
      
    • putting spaces only after = with local will assign an empty string to the variable and either create/set another empty variable or result in a context error, depending on whether the intended value would be a viable parameter name:

       % local action= "echo" 
      

      This empties action and echo

       % local action= "echo foo"
       local: not valid in this context: echo foo
      

      Here action is emptied, too, but local fails after because echo foo is not a viable parameter name due to the included space.