capture the command that launched the running process in my shell - 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...

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
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)
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.
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.

Related

Save all 'ssh' commands to a file

I log into a lot of servers via ssh. Typically I just use the easy bash history search to scroll through my history of 'ssh' commands and find the one I want. However, eventually my .bash_history reaches its limit and I start losing entries, despite increasing the limits, etc..
I would rather just adjust my $PROMPT_COMMAND so that after every command, it checks to see if I ran a command with ssh, and if so, appends that command to a file somewhere.
I saw some relevant questions asked here and here but I am struggling to understand how to use these in a function that I can source from my .bashrc and add to my $PROMPT_COMMAND that will check if the last command entered started with ssh and copy it to a file.
For example, this does not work:
$ PROMPT_COMMAND="echo; foo; "
$ foo () { echo "command was: $BASH_COMMAND" ; }
$ ssh cn-0030
...(Ctrl-D)...
$ logout
Connection to cn-0030 closed.
command was: echo "command was: $BASH_COMMAND"
This also does not work:
$ foo () { echo !! | grep ssh --color ; }
Because !! gets expanded into the actual last command run immediately and then saved into foo, instead of being evaluated when foo is evaluated

Bash control flow using || on function, with set -e

If I put set -e in a Bash script, the script will exit on future errors. I'm confused about how this works with functions. Consider the following, which will only print one to standard out:
set -e # Exit on error
fun(){
echo one
non_existing_command
echo two
}
fun
Clearly, the non_existing_command is an error and so the script exits before the second echo. Usually one can use the or operator || to run another command if and only if the first command fails. That is, I would suspect the following to print out both one and three, but not two:
set -e # Exit on error
fun(){
echo one
non_existing_command
echo two
}
fun || echo three
What I get however is one and two. That is, the || operator prevents the exit (as it should) but it chooses to continue with the function body and disregard the right-hand command.
Any explanation?
It appears to be documented in the set builtin command
If a compound command or shell function executes in a context where -e is being ignored [such as on the left-hand of a ||], none of the commands executed within the compound command or function body will be affected by the -e setting, even if -e is set and a command returns a failure status.
Emphasis and comment are mine.
Also, if you try to set -e within the function, don't bother: the next sentence:
If a compound command or shell function sets -e while executing in a context where -e is ignored, that setting will not have any effect until the compound command or the command containing the function call completes.

bash which OR operator to use - pipe v double pipe

When I'm looking at bash script code, I sometimes see | and sometimes see ||, but I don't know which is preferable.
I'm trying to do something like ..
set -e;
ret=0 && { which ansible || ret=$?; }
if [[ ${ret} -ne 0 ]]; then
# install ansible here
fi
Please advise which OR operator is preferred in this scenario.
| isn't an OR operator at all. You could use ||, though:
which ansible || {
true # put your code to install ansible here
}
This is equivalent to an if:
if ! which ansible; then
true # put your code to install ansible here
fi
By the way -- consider making a habit of using type (a shell builtin) rather than which (an external command). type is both faster and has a better understanding of shell behavior: If you have an ansible command that's provided by, say, a shell function invoking the real command, which won't know that it's there, but type will correctly detect it as available.
There is a big difference between using a single pipe (pipe output from one command to be used as input for the next command) and a process control OR (double pipe).
cat /etc/issue | less
This runs the cat command on the /etc/issue file, and instead of immediately sending the output to stdout it is piped to be the input for the less command. Yes, this isn't a great example, since you could instead simply do less /etc/issue - but at least you can see how it works
touch /etc/testing || echo Did not work
For this one, the touch command is run, or attempted to run. If it has a non-zero exit status, then the double pipe OR kicks in, and tries to execute the echo command. If the touch command worked, then whatever the other choice is (our echo command in this case) is never attempted...

What is the bash variable $COMP_LINE and what does it do?

What is the $COMP_LINE variable in bash scripting? The Bash Reference Manual has the following to say.
$COMP_LINE
The current command line. This variable is available only in shell functions and external commands invoked by the programmable completion facilities (see Programmable Completion).
I don't understand what 'the current command line' means.
I am trying to pick apart this script: to see how it manages to intercept bash commands.
hook() {
echo "$#"
}
invoke_hook() {
[ -n "$COMP_LINE" ] && return
[ "$BASH_COMMAND" = "$PROMPT_COMMAND" ] && return
local command=`history 1 | sed -e "s/^[ ]*[0-9]*[ ]*//g"`;
hook "$command"
}
trap 'invoke_hook' DEBUG
I am running into trouble figuring out what the following line is supposed to do.
[ -n "$COMP_LINE" ] && return
I assume it some sort of check or test before you run the rest of the script, since [] is an alias for the bash test command, but since I can't read it I can't figure out what it's supposed to be testing.
In Bash, if you have a file myfile.txt, you can edit it with nano myfiTab. This completes the filename automatically to save you typing, turning the command into nano myfile.txt. This is known as filename completion.
However, not all commands accept filenames. You may want to be able to do ssh myhoTab and have it complete to ssh myhostname.example.com.
Since bash can't possibly be expected to maintain this logic for all known and unknown commands across all systems, it has programmable completion.
With programmable completion, you can define a shell function to call that will get all hostnames from .ssh/known_hosts and make them available as completion entries.
When this function is invoked, it can examine the variable $COMP_LINE to see the command line it should give suggestions for. If you have set up complete -F myfunction ssh and type ssh myhoTab, then myfunction will run and $COMP_LINE will be set to ssh myho.
This functionality is used by your snippet to make the interceptor ignore commands run as a result of pressing Tab. Here it is with comments:
# This is a debug hook which will run before every single command executed
# by the shell. This includes the user's command, but also prompt commands,
# completion handlers, signal handlers and others.
invoke_hook() {
# If this command is run because of tab completion, ignore it
[ -n "$COMP_LINE" ] && return
# If the command is run to set up the prompt or window title, ignore it
[ "$BASH_COMMAND" = "$PROMPT_COMMAND" ] && return
# Get the last command from the shell history
local command=`history 1 | sed -e "s/^[ ]*[0-9]*[ ]*//g"`;
# Run the hook with that command
hook "$command"
}

Using Return in bash

I'm wanting this script to print 1,2,3.. without the use of functions, just execute two.sh then carry on where it left off, is it possible?
[root#server:~]# cat testing.sh
#!/bin/bash
echo "1"
exec ./two.sh
echo "3"
[root#server:~]# cat two.sh
#!/bin/bash
echo "2"
return
exec, if you give it a program name a, will replace the current program with whatever you specify.
If you want to just run the script (in another process) and return, simply use:
./two.sh
to do that.
For this simple case, you can also execute the script in the context of the current process with:
. ./two.sh
That will not start up a new process but will have the side-effect of allowing two.sh to affect the current shell's environment. While that's not a problem for your current two.sh (since all it does is echo a line), it may be problematic for more complicated scripts (for example, those that set environment variables).
a Without a program name, it changes certain properties of the current program, such as:
exec >/dev/null
which simply starts sending all standard output to the bit bucket.
Sure, just run:
echo "1"
./two.sh
echo "3"

Resources