How do I get the command history in a screen session using Bash? - bash

If I start a screen session with screen -dmS name, how would I access the command history of that screen session with a script?
Using the ↑, the last executed command appears, even in screen.

I use the default bash shell on my system and so might not work with other shells.
this is what I have in my ~/.screenrc file so that each new screen window gets its own command history:
Default Screen Windows With Own Command History
To open a set of default screen windows, each with their own command history file, you could add the following to the ~/.screenrc file:
screen -t "window 0" 0 bash -ic 'HISTFILE=~/.bash_history.${WINDOW} bash'
screen -t "window 1" 1 bash -ic 'HISTFILE=~/.bash_history.${WINDOW} bash'
screen -t "window 2" bash -ic 'HISTFILE=~/.bash_history.${WINDOW} bash'
Ensure New Windows Get Their Own Command History
The default screen settings mean that you create a new window using Ctrl+a c or Ctrl+a Ctrl+c. However, with just the above in your ~/.screenrc file, these will use the default ~/.bash_history file. To fix this, we will overwrite the key bindings for creating new windows. Add this to your ~/.screenrc file:
bind c screen bash -ic 'HISTFILE=~/.bash_history.${WINDOW} bash'
bind ^C screen bash -ic 'HISTFILE=~/.bash_history.${WINDOW} bash'
Now whenever you create a new screen window, it's actually launching a bash shell, setting the HISTFILE environmental variable to something that includes the current screen window's number ($WINDOW).
Command history files will be shared between screen sessions with the same window numbers.
Write Commands to $HISTFILE on Execution
As is normal bash behavior, the history is only written to the $HISTFILE file by upon exiting the shell/screen window. However, if you want commands to be written to the history files after the command is executed, and thus available immediately to other screen sessions with the same window number, you could add something like this to your ~/.bashrc file:
export PROMPT_COMMAND="history -a; history -c; history -r; ${PROMPT_COMMAND}"

screen doesn't maintain a history of the commands you type. Your shell may or may not keep a history. Since you appear to use bash, you can use the history command.
screen does appear to have a crude approximation of a history search (it merely searches the scrollback buffer for a command line. See the screen man page under the "history" command (bound to C-a { by default).

#technosaurus is right. $HISTFILE is written when bash exits, so you could exit one bash session, and start a new one, and the history should have been preserved through the file.
But I think there is a better way to solve your problem. The bash manual includes a description of the history built-in command. It allows you to save this history with history -w [filename] and read the history with history -r [filename]. If you don't provide a filename, it will use $HISTFILE.
So I would propose that you save the history inside your screen session to a specific file (or to your default $HISTFILE if you want). Then read the history file in the other bash session you want to access the history from. This way you don't have to exit the original bash session.

When you exit a terminal (or shell) the shell writes its history to $HISTFILE, so to get its history in another terminal you can type exit in the terminal you want the history of and it will get written.
cat $HISTFILE
#or tac, less, $EDITOR, ... depending on how you want to "access" it

use this:
screen -L
with capital L
it will store a copy of terminal input and output to a file named screenlog.0
or if you use -S to name it, the log file gets the screen name

I put the next lines into my .bashrc:
case "$TERM" in
screen)
declare SCREEN_NAME=$(echo $STY | sed -nr 's/[^.]*\.(.*)/\1/p')
if [[ $SCREEN_NAME ]]; then
HISTFILE="${HISTFILE}.${SCREEN_NAME}.${WINDOW}"
declare -p HISTFILE
fi
unset SCREEN_NAME
;;
*)
;;
esac
My default .bashrc has this 'case' basically with 'xterm*|rxvt*)' value, so I only added my 'screen' part into it. If you have not this 'case', you can use the next instead of it:
if [[ $TERM == screen ]]; then
declare SCREEN_NAME=$(echo $STY | sed -nr 's/[^.]*\.(.*)/\1/p')
if [[ $SCREEN_NAME ]]; then
HISTFILE="${HISTFILE}.${SCREEN_NAME}.${WINDOW}"
declare -p HISTFILE
fi
unset SCREEN_NAME
fi
And after I have an own bash_history for all window of my all screen.
Note: this not work in chroot!

history will show all the history command.

Related

Clear last bash command from history from executing bash script

I have a bash script, which uses expect package to ssh into a remote server.
My script looks like this:
#!/bin/bash
while getopts "p:" option; do
case "${option}" in
p) PASSWORD=${OPTARG};;
esac
done
/usr/bin/expect -c "
spawn ssh my.login.server.com
expect {
\"Password*\" {
send \"$PASSWORD\r\"
}
}
interact
"
I run it like ./login.sh -p <my-confidential-password>
Now once you run it and log in successfully and exit from the remote server, I can hit up-arrow-key from the keyboard and can still see my command with password in the terminal. Or I simply run history it shows up. Once I exit the terminal, then it also appears in bash_history.
I need something within my script that could clear it from history and leave no trace of the command I ran (or password) anywhere.
I have tried:
Clearing it using history -c && history -r, this doesn't work as the script creates its own session.
Also, echo $HISTCMD returns 1 within script, hence I cannot clear using history -d <tag>.
P.S. I am using macOS
You could disable command history for a command:
set +o history
echo "$PASSWORD"
set -o history
Or, if your HISTCONTROL Bash variable includes ignorespace, you can indent the command with a space and it won't be added to the history:
$ HISTCONTROL=ignorespace
$ echo "Hi"
Hi
$ echo "Invisible" # Extra leading space!
Invisible
$ history | tail -n2
7 echo "Hi"
8 history | tail -n2
Notice that this isn't secure, either: the password would still be visible in any place showing running processes (such as top and friends). Consider reading it from a file with 400 permissions, or use something like pass.
You could also wrap the call into a helper function that prompts for the password, so the call containing the password wouldn't make it into command history:
runwithpw() {
IFS= read -rp 'Password: ' pass
./login.sh -p "$pass"
}

for-Loop in screen does not work

I would like to use screen to stay attached to a loop command on a ssh session, which is most likely going to run for a couple of hours. I am using screen because I fear that my terminal will get disconnected while the command is still running. This is the loop-command:
for i in *; do echo $i/share/sessions/*; done
(echo will be replaced by rm -rf).
I have tried multiple variants of screen 'command ; command ; command', but never got it working. How can I fix this? Alternatively, could you suggest a workaround for my problem?
Screen for long running commands can be used like this :
$screen -S session_name
//Inside screen session
$ <run long running command>
$ //press key combination - Ctrl + a + d - to come out of screen session
// Outside screen session
// Attach to previously created session
$screen -x session_name
For more details look at man page of screen.
Another application which works similar way and is very popular is tmux
I assume that you're trying to run:
screen 'for i in *; do echo $i/share/sessions/* ; done'
This results in a Cannot exec [your-command-here]: No such file or directory because screen doesn't implicitly start a shell; rather, it calls an execv-family syscall to directly invoke the program named in its argument. There is no program named for i in *; do echo $i/share/sessions/*; done, and no shell running which might interpret that as a script, so this fails.
You can, however, explicitly start a shell:
screen bash -c 'for i in *; do echo $i/share/sessions/* ; done'
By the way -- running one copy of rm per file you want to delete is going to be quite inefficient. Consider using xargs to spawn the smallest possible number of instances:
# avoid needing to quote and escape the code to run by encapsulating it in a function
screenfunc() { printf '%s\0' */share/sessions/* | xargs -0 rm -rf; }
export -f screenfunc # ...and exporting that function so subprocesses can access it.
screen bash -c screenfunc
There is no need really for screen here.
nohup rm -vrf */share/sessions/* >rm.out 2>&1 &
will run the command in the background, with output to rm.out. I added the -v option so you can see in more detail what it's doing by examining the tail of the output file. Note that the file won't be updated completely in real time due to buffering.
Another complication is that the invoking shell will do a significant amount of work with the wildcard when it sets up this job. You can delegate that to a subshell, too:
nohup sh -c 'rm -rvf */share/sessions/*' >rm.out 2>&1 &

How can I make TMUX be active whenever I start a new shell session?

Instead of having to type tmux every time, how could I have tmux always be used for new session windows?
So if I have no terminal windows open and then I open one, how can that first session be in tmux?
Seems like a .bashrc sort of thing perhaps?
warning this can now 'corrupt' (make it unable to open a terminal window - which is not good!) your Ubuntu logins. Use with extreme caution and make sure you have a second admin account on the computer that you can log into in case you have the same problems I did. See my other answer for more details and a different approach.
Given that warning, the simplest solution can be to append the tmux invocation to the end of your .bashrc, e.g.
alias g="grep"
alias ls="ls --color=auto"
# ...other stuff...
if [[ ! $TERM =~ screen ]]; then
exec tmux
fi
Note that the exec means that the bash process which starts when you open the terminal is replaced by tmux, so Ctrl-B D (i.e. disconnect from tmux) actually closes the window, instead of returning to the original bash process, which is probably the behaviour you want?
Also, the if statement is required (it detects if the current bash window is in a tmux process already) otherwise each time you start tmux, the contained bash process will attempt to start its own tmux session, leading to an infinite number of nested tmuxen which can be, err, quite annoying (that said, it looks cool).
However, there is a very small risk this can make bash behave in a way that other programs don't expect, since running bash can possibly cause it to turn into a tmux process, so it might be better to modify how you start your terminal emulator.
I use a small executable shell script ~/bin/terminal (with ~/bin in $PATH, so it is found automatically) that looks a bit like:
#!/bin/sh
exec gnome-terminal -e tmux
(I don't use gnome-terminal, so you might have to remove the exec, I'm not sure.)
Now whenever you run the terminal scipt you have a terminal with tmux. You can add this to your menu/desktop/keyboard shortcuts to replace the default terminal.
(This approach also allows you to more easily customise other things about the terminal emulator later, if you ever desire.)
My original, accepted answer, stopped working on my Ubuntu14 system after a recent upgrade.
Using either
[ -z "$TMUX" ] && command -v tmux > /dev/null && TERM=xterm-256color && exec tmux
or
[ $TERM != "screen" ] && TERM=xterm-256color && exec tmux
would stop me from being able to even login. I was only able to resolve this due to having a second admin login on the computer.
The fix for me on Ubuntu (and in osx too) was to change my terminal program to actually run tmux instead, i.e.
I still have
[ `uname -s` != Linux ] && exec tmux
as my last .bashrc line but that his only for my Mac OSX systems now.
If you want to have a single tmux session, put the following in your ~/.bashrc for bash or ~/.zshrc for zsh:
tmux attach &> /dev/null
if [[ ! $TERM =~ screen ]]; then
exec tmux
fi
The tmux attach line is to make sure if there is a session it attaches to and if there was no session you will not get the warning about "no session".
For me, I would love my tmux to be started every time I shell onto my remote machine, and when I detach or exit from tmux, the connection should be closed automatically. After digging into this issue for a while, the following code does exactly what I want and is believed to be the most optimized to the best of my knowledge.
[ -z "$TMUX" ] && { tmux attach || exec tmux new-session && exit;}
Note this line should be the first line in you bashrc file to make sure it is loaded first. We can't put an "exec" call in front of "tmux attach" because after the exec replaces the bash process with the tmux one, the connection will be closed even if there are no sessions to attach to. Therefore we need an "exit" call to terminate the connection after we detach or exit from the attached sessions. But putting an "exec" in front the new-session command is fine as that's the last command to be executed.
Append following line of code to the end of .bashrc,
[[ $TERM != "screen" ]] && exec tmux
I just made it a keyboard shortcut (in Linux Mint not Ubuntu; so I'm not sure if it is this easy)...
It might be hard to see, but the custom shortcut is gnome-terminal --window --maximize -e tmux. This starts a new gnome-terminal window maximized and then executes tmux for you.
I additionally have another custom shortcut that starts a "normal" gnome-terminal maximized (it's the same without the -e tmux).
I feel this is the best way because you can start whatever terminal whatever way you want and is the most customizable way.
I started with this https://wiki.archlinux.org/index.php/Tmux#Bash and enhanced it to reclaim detached sessions and make new ones if all sessions were already attached
# .bashrc
case $- in
*i*)
if command -v tmux>/dev/null; then
if [[ ! $TERM =~ screen ]] && [[ -z $TMUX ]]; then
if tmux ls 2> /dev/null | grep -q -v attached; then
exec tmux attach -t $(tmux ls 2> /dev/null | grep -v attached | head -1 | cut -d : -f 1)
else
exec tmux
fi
fi
fi
;;
esac
To enable tmux for login and ssh sessions, you can add this to the end of your .bashrc:
# enable tmux at login
PNAME="$(ps -o comm= $PPID)";
if [ $PNAME == "login" ] || [ $PNAME == "sshd" ] ; then
exec tmux
fi
This script looks for the parent process of the bash shell. If bash was started from logging in or from ssh, it will execute tmux. If you want this to work with a GUI terminal, you can add that in there as well. For example, if you want to start tmux automatically when you start Ubuntu's standard gnome-terminal, you would use this:
PNAME="$(ps -o comm= $PPID)";
if [ $PNAME == "login" ] || [ $PNAME == "sshd" ] || [ $PNAME == "gnome-terminal" ] ; then
exec tmux
fi
I've tested the above on Live Ubuntu Desktop and I was able to log in afterwards. This should not break the GUI login unless it invokes the login command to log in. I am not aware of a linux GUI that does this.
Within xfce4 (I'm running Fedora 24 XFCE spin, it's great), I've found the simplest solution is to edit panel shortcuts to so they run:
xfce4-terminal -e tmux
This same command can be used to replace the Keyboard Application Shortcut.
I had previously inserted an if statement into my .bashrc, but it caused login to fail (loop back to the login box whenever a correct password was entered).
The command for Thunar's Open Terminal Here command differs slightly. To change that goto:
Thunar > Edit > Configure Custom Actions... > Open Terminal Here > Edit button, and replace:
exo-open --working-directory %f --launch TerminalEmulator
with: xfce4-terminal --working-directory %f -e tmux
A one-liner that also makes sure the terminal type is set correctly for 256 colors:
[ -z "$TMUX" ] && export TERM=xterm-256color && exec tmux
How about adding
# If not running interactively, do not do anything
[[ $- != *i* ]] && return
[[ -z "$TMUX" ]] && exec tmux
to your .bashrc. It also works for zsh.
Taken from https://wiki.archlinux.org/index.php/Tmux#Start_tmux_with_default_session_layout

shell: how to make tail of a file running in background

I want to run a few tasks in shell.
tail a file into a new file: for example: tail -f debug|tee -a test.log
at the same time, run other tasks.
My question is: how to make the command tail -f debug|tee -a test.log run in background, so that I can run other tasks then in shell script?
You don't need tee at all for this, just use the shell's built-in append operator:
tail -f debug >> test.log &
The trailing & works as normal in the shell. You only need tee to send the output to a file as well as standard out, which if you have it in the background probably isn't what you want.
Normally you just use an ampersand after the command if you want to background something.
tail -f debug|tee -a test.log &
Then you can bring it back to the foreground later by typing fg. Did this answer your question or have I missed what you were asking?
The simple way to do this is:
screen -R -D
tail -f debug|tee -a test.log
Ctrl-A c
ps ax |grep tail
Ctrl-A [Backspace]
...
Ctrl-A [Spacebar]
screen lets you run multiple terminal sessions on one terminal connection. You switch back and forth with Ctrl-A [Backspace]|[Space]. To create another separate shell Ctrl-A c
A major benefit of screen is that if the terminal session disconnects, it keeps everything runnning. Just close the terminal window or disconnect ssh, go to another computer, log in and run screen -R -D to reconnect to everything which is still running.
If you only occasionally need this, just run tail, type Ctrl-Z, run a command, then fg %1 to bring the tail process back into the foreground, or bg %1 to make it run in the background permanently. If you do use Ctrl-Z, then the jobs command shows all of your detached jobs.

How to escape pipes in .screenrc for commands to run at startup?

I'm using byobu/screen, and I would like to have a new screen session default to containing a few windows set up specially for tailing a specific log file.
My .screenrc looks something like this (technically this is my .byobu/windows file):
chdir /home/matt/code/project/logs
screen -t 'logs' tail -F current.log
chdir /home/matt/code/project
screen -t 'errors' tail -F current.log | grep -A 3 "ERROR"
chdir /home/matt/code/project
screen -t 'project'
chdir
screen -t 'bash'
My intention is to set up four windows in the new screen session:
A window titled "logs" which tails the current.log file
A window titled "errors" which tails the current.log file and greps for ERROR
A window titled "project" which starts in my project's main directory
A window titled "bash" which starts in my home directory.
However, the pipe in the screen -t 'errors' tail -F current.log | grep -A 3 "ERROR" command ends up being interpreted by screen literally, and thus my second window never appears.
How can I escape the pipe in this command to have it interpreted as I wish?
Furthermore, is there an easier way to setup screen/byobu to launch windows that are running (complicated) commands at startup?
I ended up solving this by using the stuff command to simulate entering a command in the window and pressing enter to execute it. This has the nice effect of making it possible to break out of the tail command in the screen window without also killing the window itself.
Here is an example of what my .screenrc looks like to accomplish this; I've written up a longer explanation on my blog:
screen -t 'errors'
stuff 'tail -F /var/ec/current.log | grep -A 3 "ERROR"^M'
(the ^M is entered by pressing Ctrl+V, Enter with your keyboard, not by actually typing caret and uppercase M)
The following works for me:
screen -t errors bash -c "tail -F current.log | grep -A 3 ERROR"
The use of bash (or other shell) is required to prevent screen from giving a "file not found"-error, which will be the result if bash -c is removed from the above.
You should be fine with creating custom script and use it in your .screenrc - so you would have screen -t 'error' ./bin/current.log.sh
And tail -F ... in current.log.sh

Resources