18 1 / 2012
Asynchronous GNU Readline printing
Some while ago I’ve spend my time developing a XMPP command line client which is using strophe XMPP library to handle XMPP and GNU Readline for I/O.
The idea was to have a readline prompt at the bottom and yet be able to asynchronously print incoming messages above it - in the “log window”.
It seems that many people were looking for solution already:
- GNU Readline: how do clear the input line?
- Using GNU Readline; how can I add ncurses in the same program?
I haven’t found any satisfying answer on the web so I’d like to present my own solution.
Basic idea is to use alternate (asynchronous) GNU Readline interface and on each new asynchronous print:
- save a copy of current line state
- clear both prompt and current line (content + position)
- force screen update
- print asynchronous event (followed by a newline)
- restore prompt and current line state
- force screen update
Simple it is, indeed and you can see a working code if you don’t belive.
The only thing that I was unable to get is preventing putting the original confirmed readline buffer in “log window”. As this is not a big deal for my requirements the complete and universal solution would be able to change what the user typed in the readline buffer just before it’s getting scrolled up and becoming part of the “log window”.
I hope someone will fine it useful, like I do.
Permalink 11 notes
18 1 / 2012
Teach your shell how to grab you attention
In my daily work it’s often a case when I have long-running process that I have to wait for to complete and I don’t want to stare at the shell all the time.
To do what I want I’ve created two commands. First is tick that I store under $HOME/bin/tick:
#!/bin/sh
if which mplayer 1>/dev/null ; then
mplayer -quiet -nolirc "$HOME/usr/sounds/tick.ogg" 1>/dev/null 2>/dev/null &
fi
I use it during daily work, and in some of my scripts to play simple, short “tick”.
Second is notify, which is a bit more extensive notification command. The content of $HOME/bin/notify is:
#!/bin/sh
if which mplayer 1>/dev/null; then
mplayer -quiet -nolirc "$HOME/usr/sounds/alarm.ogg" 1>/dev/null 2>/dev/null &
fi
if which notify-send 1>/dev/null ; then
notify-send -t 3000 --hint=int:transient:1 -- "$1" "$2"
fi
Not only it plays longer and louder alarm sound, but additionally it uses notify-send to display notification on your desktop so you know exactly what happened.
Integrate tick and notify into ZSH
In a depth of my complex ZSH configuration I do something like this:
function preexec {
export ZSH_CMD_TICK_TS=`date '+%s'`
export ZSH_CMD_TICK_CMD="$1"
}
function precmd {
line-tick
}
export ZSH_CMD_TICK_TS=`date '+%s'`
function line-tick() {
TS=`date '+%s'`
if [ $(($ZSH_CMD_TICK_TS + 60)) -lt $TS ]; then
notify "Cmd finished: $ZSH_CMD_TICK_CMD"
elif [ $(($ZSH_CMD_TICK_TS + 10)) -lt $TS ]; then
tick
fi
export ZSH_CMD_TICK_TS=$TS
}
So what does it do? First, before execution of every command preexec will record current timestamp and command that is about to be executed. Then, after command has finished, but before the new prompt is displayed precmd will call line-tick to check what should be done. line-tick will compare the current timestamp with the one recorded by preexec. If the time spent on execution of the command is longer than 60 seconds, notify command will be triggered to tell us what command has finished and play alarm sound. If the command execution time was smaller, but still longer than 10 seconds tick will be used instead.
Permalink 9 notes
10 1 / 2012
Add some structure to your home directory.
Personally I find it very useful to keep the same structure on every Unix account I’m using. This is somehow connected with the idea of keeping dot-files in sync between all of them.
I loosely base the structure on Unix root filesystem structure:
~/bin- my handy scripts~/doc- documents of any kind~/etc- files keeping settings of~/binscripts~/lab- where I do my programming tasks~/tmp- temporary files, downloads~/opt- account-local software
More on ~/opt
It’s often a case that one has to use custom software on non-root account. In such cases I just download the software source to ~/opt/src, extract it, go to extracted dir and do:
./configure.sh --prefix="$HOME/opt"
make && make install
or similar procedure, depending on the software building system.
For this to fully and seamlessly work, on have to modify some system paths. Using append_env function I’m doing something like:
prepend_env PATH "$HOME/bin:$HOME/opt/bin:$HOME/opt/usr/bin"
prepend_env LD_LIBRARY_PATH "$HOME/opt/lib"
prepend_env LD_RUN_PATH "$HOME/opt/lib"
prepend_env PKG_CONFIG_PATH "$HOME/opt/lib/pkgconfig"
prepend_env LIBRARY_PATH "$HOME/opt/lib"
prepend_env C_INCLUDE_PATH "$HOME/opt/include"
[ -z "$MANPATH" ] && export MANPATH="`manpath`"
prepend_env MANPATH "$HOME/opt/share/man"
09 1 / 2012
Keep you dot-files in sync.
To work efficiently it’s necessary to customize and personalize a lot. After some time one gets used to his customizations and start to depend on them. For example: I map Caps Lock to Escape, as I tend to use vi-mode in any software that supports it and I find Caps Lock key useless anyway. After years of doing so, I simple can not work if Caps Lock is not mapped to Escape. That’s why it’s important for me to be able to easily import my settings to any account I am given: on my personal computer, remote shell, or temporary account.
For this I use git. Generally, using git for revisioning dot-files is not recommended, but in practice it works really well. For every Unix account I am to use, first thing I am doing is:
cd
git init .
git remote add origin <path-to-my-home-skeleton-repository>
git fetch
git checkout master
I log out, and then log in, and I’m able to work the way I’m used to.
On every computer, there might be reason for local customization. That’s why I revision only a common settings, and often include local-only settings.
E.g. in my .zshrc I use:
[ -f "$HOME/.zshrc.local" ] && source "$HOME/.zshrc.local"
With this approach, I can have things specific for particular machine/account in a file, that I don’t put under revision control.
Permalink 6 notes
03 1 / 2012
Prepend or append to PATH like environment variable.
In Unix there are quite a lot variables representing path lists of different kind similar to PATH like LD_LIBRARY_PATH, PKG_CONFIG_PATH.
Usual idiom to modify these variables is:
$PATH="$PATH:/new/path/to/something"
I found it quite a lot of typing in a daily work, so I’m using functions shortening the above to just:
append_env PATH /new/path/to/something
The version for Bash is:
function append_env {
if [ -z "${!1}" ]; then
export "$1"="$2"
else
export "$1"="${!1}:$2"
fi
}
function prepend_env {
if [ -z "${!1}" ]; then
export "$1"="$2"
else
export "$1"="$2:${!1}"
fi
}
And for Zsh:
function append_env {
eval "local v=\$$1"
if [ -z "$v" ]; then
export "$1"="$2"
else
export "$1"="$v:$2"
fi
}
function prepend_env {
eval "local v=\$$1"
if [ -z "$v" ]; then
export "$1"="$2"
else
export "$1"="$2:$v"
fi
}
Permalink 1 note
29 12 / 2011
Make tmux and ssh-agent work smoothly.
A lot of people using remote shell accounts must deal with a problem of stale environment variables for ssh-agent.
At each ssh connection, new socket is created and variable $SSH_AUTH_SOCK is set to point to it. The problem is that shells that are already opened can’t have this environmental variable updated automatically. I’ve seen a lot of complex solutions to this (writing some update scripts and aliasing ssh command, etc.).
Finally I’ve realized there’s a simpler solution. Instead of updating $SSH_AUTH_SOCKET, make it point at one location only: a symlink, and update that symlink at each connection. Just as usual in Computer Science, the problem is solved by just another level of indirection.
In my personal scripts I use something much more complex, but the basic idea is:
if [ -z "$TMUX" ]; then
if [ ! -z "$SSH_TTY" ]; then
if [ ! -z "SSH_AUTH_SOCK" ]; then
ln -sf "$SSH_AUTH_SOCK" "$HOME/.wrap_auth_sock"
fi
export SSH_AUTH_SOCK="$HOME/.wrap_auth_sock"
exec "$HOME/bin/tmux-session" "sshwrap"
fi
fi
So:
- if already running inside
tmux, skip - if not connected via
ssh, skip - if
$SSH_AUTH_SOCKis defined, update the symlink - export one and only correct
$SSH_AUTH_SOCK - launch tmux
For completeness, here goes my $HOME/bin/tmux-session:
#!/bin/bash
# Reattach to (or spawn new if does not exist)
# tmux session "$1"
export STY="tmux-$1"
if tmux has-session -t "$1"; then
exec tmux attach-session -t "$1"
else
exec tmux new-session -s "$1"
fi
Permalink 5 notes