You can test the terminal by setting the cursor position to column 1 and outputing a multibyte unicode character. If the cursor moves by more than 1 position then the terminal does not support unicode.
Thursday, 25 May 2023
Wednesday, 24 November 2021
BASH inline functions or macros
Sometimes you want to write bash macros instead of bash functions.
You want to write some bash which can declare local variables in the caller context.
Maybe it parses some data and creates a bunch of hashes with a known prefix (that's as much namespace management as bash gives you) and populates a named list with the names of those hashes.
And then you need to use that twice so you want to factor it into a function. Only you can't these aren't meant to be global variables.
So you have a few solutions, all using source in one way or another
You could put it into a separate file and source it whenever you need it.
You could define it in a string or here document and source it when needed (but you loose bash syntax support in the editor).
You can call the function using command substitution and have it execute declare -p on the variables you want to export.
e.g. source <( my-function args )
Or to enjoy all the side effects directly, you can export the function body using declare -f and strip the first line (which is the function declaration) my masking it with a comment, and then execute the rest directly, using source.
First iteration
And to invoke it:
Example
Maybe source <( function-body my-function ) "$@" is a bit verbose when calling a function inline every time.
Bugs
Second iteration
Example
Note the double-eval mechanism so that <<< can be used to read the function name on stdin while the remaining arguments are applied to the body as $@ for the lifetime of the body (thanks, bash)
Bugs
Third Iteration
Example
Bugs
Bugs
Fourth Iteration
Example
Friday, 23 October 2020
dd is a pretentious fraud, don't trust it except for disks, files, and tape
Especially busybox dd.
[Note, specifying ibs and obs separately seems to work as expected (except with busybox dd). Using bs where ibs and obs have the same value is not a good idea]
I used to feel guilty writing to the tape drive with cat: cat /tmp/backup.tar.gz > /dev/rct0
What dd was good at, (I had somehow learned by osmosis) was reading and writing full block sizes where the device driver can't do it so well.
What nonsense.
dd doesn't to "impedance matching" of block sizes, it has barely any regard for them.
This all of a sudden mattered when I was using a shell to set efi variables using the efivarfs file system:
printf "\x07\x00\x00\x00%s" "$var-value" > "my-var-${owner-UUID}"
That works fine, but this was a problem:
some-process | cat /dev/fd/2 2<<<$'\x07\x00\x00\x00' - > "my-var-${owner-UUID}"
why? Because the data written to the variable pseudo-file had to be written all-in-one-go, and as this example shows, it might not happen, the output is written in two parts:
( echo hello ; sleep 1 ; echo goodbye ) | cat /dev/fd/2 2<<<$'\x07\x00\x00\x00' - | cat | cat
I supposed that this would be ideal for dd, I set the block size to 4096 which is the maximum write to the efi-var psuedo files anyway, giving:
( echo hello ; sleep 1 ; echo goodbye ) | \
cat /dev/fd/2 2<<<$'\x07\x00\x00\x00' - | dd bs=4096 of="my-var-${owner-UUID}"
but it didn't do the trick, strace showed multiple writes from dd, as we can also see here
( echo hello ; sleep 1 ; echo goodbye ) | cat /dev/fd/2 2<<<$'\x07\x00\x00\x00' - | dd bs=4096 | cat | cat
it turns out that dd doesn't care if it does a partial read from a pipe, it takes the block size as a maximum hint, rather than a requirement.
So... dd is ostensibly good at impedance matching of block sizes, lets set an input block size of 1 and an output block size of 4096 and let it accumulate the input blocks for output:
( echo hello ; sleep 1 ; echo goodbye ) | cat /dev/fd/2 2<<<$'\x07\x00\x00\x00' - | dd bs=1 obs=4096 | cat | cat
exactly the same result! dd just doesn't care!
GNU has the life-saving non-posix iflag=fullblock which actually reads a full input block (unless eof).
Without which, what is the point of dd? Well yes, we know it has incidental features such as very limited character set conversion, along with skip and seek, and unlike head, tail, etc, it won't read more input than it intends to use (which makes it very convenient for reading some of stdin from a shell script).
But it's main purpose is unmet, and with solid risk in some circles. Want to generate a random password for openssl?
password=$(dd if=/dev/random bs=32 count=1 | base64)
do you see the danger yet? What if dd blocks for want of randomness, dd will return a partial block!
simulate thus, try to read 12 random bytes and see how many we might get:
( printf "a" ; sleep 1 ; printf "bc" ; sleep 1 ; printf "def" ; sleep 1 ; printf "ghij" ; sleep 1 ; printf "klmno" ) | dd bs=12 count=1 | wc -c
we get one character instead of 12 (or instead of 256 or however many you expected)
The fix is to swap bs and count, so we keep reading 1 byte until we have enough
( printf "a" ; sleep 1 ; printf "bc" ; sleep 1 ; printf "def" ; sleep 1 ; printf "ghij" ; sleep 1 ; printf "klmno" ) | dd bs=1 count=12 | wc -c
dd, you are a pretentious ass, without the GNU extension you cannot read lots of small blocks and write one big block, and even on a non-terminating stream with more data becoming available, you will read less than block size multiplied by count, and think yourself smart!
Except with disks, files, and tape, where the driver managers block size, and partial reads won't happen, you can't be trusted to get your most basic task right.
Many interesting remarks at:
- https://unix.stackexchange.com/questions/121865/create-random-data-with-dd-and-get-partial-read-warning-is-the-data-after-the
- https://unix.stackexchange.com/questions/17295/when-is-dd-suitable-for-copying-data-or-when-are-read-and-write-partial
- https://superuser.com/questions/520601/why-does-dd-only-copy-128-bytes-from-dev-random-when-i-request-more
Saturday, 21 March 2020
Working with ttystudio and ttyrec
Sadly it is slow, and uses up way to much memory -- even running out of memory for even a small session.
Ironically it boasts of not using imagemagick while requiring node! No wonder it runs out of memory. Sadly the frams also seem to be based on clock time instead of ttyrec frames.
Even worse, it only works for recording live sessions.
This shell script causes ttystudio to convert a ttyrec file.
Use: ./ttyrec2studio ttyrecfilename
to make ttyrecfilename.gif
#! /bin/bash
ttystudio() {
local ttyrec="$1"
local fifo=$(mktemp -u)
mkfifo "$fifo"
coproc ttypipe { SHELL=$(realpath "$0") QUITPIPE="$fifo" TTYPLAYFILE="$ttyrec" command ttystudio "$ttyrec.gif" >/dev/tty 2>&1 ; }
# when we've finished with fifo, ttyplay has finished
cat "$fifo"
# send ^Q
printf $'\x11' >/proc/$$/fd/${ttypipe[1]}
# close pipe to coproc
eval "exec ${ttypipe[1]}<&-"
# Drain pipe from coproc
#eval "exec ${ttypipe[0]}<&-"
eval "cat /proc/$$/fd/${ttypipe[0]}"
wait $! # is coproc
echo Done all $?
}
emit() {
rm -f "$QUITPIPE"
echo "Playing $1" >&3
ttyplay "$1"
echo "Finished playing $1 [$?]" >&3
} 3>"$QUITPIPE"
main() {
if test -z "$QUITPIPE"
then ttystudio "$@"
else emit "$TTYPLAYFILE"
fi
}
main "$@"
Thursday, 18 July 2019
readline with bash's read -e
Thursday, 6 June 2019
synchronous pipe based task monitoring
An obvious way if I control the launch is to tie them together with a pipe and they can detect when the pipe closes.
Maybe the other process would launch many further processes, all inheriting the pipe-fd, which I want to avoid.
So clearly the launcher needs to hold the pipe-fd but not share it (close-on-exec, don't fork too much), and then wait in the usual way for the launched process to quit, and then close the pipe.
Here's a bash incantation lifetime_fd which runs a simple list of arguments and can share an fd with another process through process substitution.
# just run a simple list, without affecting $?
just() {
set -- $? "$@" "${@:2}"
return $1
}
lifetime_fd() {
set -- $_ "$@" ; eval "$1>&-" '"${@:2}"' ; just eval exec "$1>&-"
}
So if you want to run command fooly barly bazly and link it to the lame read && echo done then this will do the trick
lifetime_fd fooly barly bazly {_}> >( read && echo done )
The variable $_ is used to avoid messing with any other variables. $_ is constantly adjusted and should do no harm if we abuse it; but as it is constantly adjusted, the first thing we do in lifetime_fd is to save it.
We don't use a local variable in case of a name clash that affects something else, so we store it as $1
We then run "$@" (or "${@:2}" as it now would be) but with the fd closed, so that it is not inherited.
We then close the fd while preserving the exit code.
You can invoke it in a pipeline like this:
get_report_request | lifetime_fd get_report {_}> >( monitor ) | send_report
An illustrative example of monitor (which reads until eof), might be:
monitor() {
while read -t 1 || test $? = 142 # 142 is timeout code
do echo -n '*'
done
}
which displays a star every second until stdin closes; by continually waiting up to 1 second to fail to read anything from stdin (until it closes, having a different exit code), and displays a star.
Of course it might read other data too.... if you can send it...
Tuesday, 7 June 2016
Defining bash functions in a Makefile
Because of bash's export -f function-name feature to export a bash function definition into the environment where it will be picked up by sub-child invocations of bash, we can define a bash variable in the environment in the same form in the Makefile.
If we define bash functions in the Makefile using the same format bash does, and export them into the environment, and set SHELL=bash then we can use these bash functions in the make recipe.
Let's see how bash formats functions exported in the environment:
$ # define the function
$ something() { echo do something here ; }
$ export -f something
$ # the Makefile
$ cat > Makefile <<END
SHELL=bash
all: ; something
END
$
Try it out
$ make
something
do something here
$
How does this look in the environment?
$ env | grep something
BASH_FUNC_something%%=() { echo do something here
The pattern is (currently):
BASH_FUNC_function-name%%=() { function-body }
So we do this in the Makefile:
SHELL=bash
define BASH_FUNC_something-else%%
() {
echo something else
}
endef
export BASH_FUNC_something-else%%
all: ; something-else
and try it:
$ make
something-else
something else
It's a bit ugly in the Makefile but presents no ugliness for make -n
Thursday, 26 May 2016
Parsing paths & deleting empty items from a bash list
IFS=/ read -d "" -r -a path <<<"$path"
but because of the action of <<<, the final item will have a newline appended -- but that can be removed thus:
path[-1]="${path[-1]%$'\n'}"
If the path began with a /, then path[0] will be empty, so as the leading / is important, we'll recover that
test -z "${path[0]}" && path[0]="/"
This spell will remove empty items from the list (and also renumber the indexes):
IFS= eval 'path=(${path[@]})'
(We could avoid the eval by saving IFS, but eval is safe enough here not to bother).
Now it is simple enough to iterate over "${path[@]}" and perform a chdir on each stage.
Monday, 1 February 2016
Using flock in bash without invoking a subshell
Inspired by Ivan's post http://blog.famzah.net/2013/07/31/using-flock-in-bash-without-invoking-a-subshell I've written a flock wrapper for bash, that uses flock underneath but allows flock -c to work for bash functions.
It can be called just like the regular flock command, with the benefit that the -c invocation is supported for bash functions; so you can use it like this:
flock -o /tmp/process my_thing "$@"
and I strongly recommend the -o option so that the file descriptor used for the lock is not passed to any sub-processes, which could be problematic if long live sub-processes (e.g. re-spawned daemons) keep it open.
It will pass through and invoke the regular flock if the command isn't a bash function, or if you aren't trying to execute a function.
Sadly, it doesn't recognize bash built-in's.
But the good news is, you can use flock -o ...file... on a shell function inside your shell script without having to worry about file descriptors.
# Helper function (in order to preserve $@ in the caller)
# If this isn't used to call a shell function then return 0
# otherwise return $? as the argumment number which represents
# the command/function to be called
_is_standard_flock() {
local args=$#
# find the first argument that doesn't begin with a -
while test $# != 0
do case "$1" in
-*) shift ; continue ;;
*) break ;;
esac
done
# if it is numeric and there are not additional arguments
test $# = 1 -a -n "$1" -a -z "${1//[0-9]}" && return 0
shift
# (skipping -c if present)
# or the following argument is also not a shell function then use the original flock
if test "$1" = "-c"
then declare -F "$2" >/dev/null || return 0
else declare -F "$1" >/dev/null || return 0
fi
# we can't have shifted many args if this is a legitimate use of flock
# so we will be in range of the exit code
return $(( args - $# + 1))
}
# Help function to determine if -o or --close was given in the flock arguments
_wants_close() {
test "${*/#--close/}" != "$*" && return # will also match bogus arguments like --closed
# remove any -- options
set -- "${@/#--*/}"
# look for options with o
test "${*/#-*o/}" != "$*" && return
return 1
}
flock() {
if _is_standard_flock "$@"
then : # do outside the if-clause so bash can optimise exec where possible
else # save the exit code (offset) as $1
set -- $? "$@"
# ${!$1} is the lock file
# ${@:$(($1 + 1))} might be -c
test "${@:$(($1 + 1)):1}" = "-c" && set -- "${@:1:$1}" "${@:$(($1 + 2))}"
if _wants_close "${*:2:$(( $1 - 1))}"
then { set -- "$1" "$_" "${@:2}" ; command flock "${@:3:$(( $1 - 2))}" $2 && eval '"${@:$(( $1 + 2))}"' "$2>&-" ; set -- $? $2 ; command flock -u $2 ; return $1 ; } {_}<"${!1}"
else { set -- "$1" "$_" "${@:2}" ; command flock "${@:3:$(( $1 - 2))}" $2 && "${@:$(( $1 + 2))}" ; set -- $? $2 ; command flock -u $2 ; return $1 ; } {_}<"${!1}"
fi
fi
command flock "$@"
}
x
Wednesday, 27 January 2016
Bash: setting and testing $?
?() {
return ${1:-$?}
}
and then:
$ \? 2 $ echo $? 2
You also want to test $?:
$ \? 2 $ \? && echo yes $? || echo no $? no 2
which is simpler than
$ test $? = 0 && echo yes $? || echo no $? no 1which also replaces $? with the result of the test.
But how about this extended form that allows you to run a command while preserving (or forcing) the return code?
?() {
set -- $? "$@"
if test "$#" -le 2 -a -z "${2//[0-9]}"
then return ${2:-$1}
else "${@:2}"
return $1
fi
}
e.g.:
$ tar -xzf "$tar" $ \? rm -fr "$tar"
which leaves $? set to the result of the tar extraction.
Of course a command which is purely numeric with no arguments is mistaken for an exit code.
Note the use of: set -- $? "$@" as function arguments are which is the only lexical scoped variables in bash.
Timing bash commands
timer () {
{ time { "$@" ; } 2>${_} {_}>&- ; } {_}>&2 2>"/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
set -- $?
read -d "" _ real _ user _ sys _ < "/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
rm -f "/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
return $1
}
e.g.
timer find /bin /sbin /usr rm /tmp/
echo $real $user $sys
note: it only times a simple command, not any part of a pipeline (all parts of which are run in a sub-shell).
This version allows you to specify as $1 $2 $3 the name of the variables that should receive the 3 times:
timer () {
{ time { "${@:4}" ; } 2>${_} {_}>&- ; } {_}>&2 2>"/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
set -- $? "$@"
read -d "" _ "$2" _ "$3" _ "$4" _ < "/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
rm -f "/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
return $1
}
e.g.
timer r u s find /bin /sbin /usr rm /tmp/
echo $r $u $s
and may be useful if it ends up being called recursively, to avoid trampling on times; but then r u s etc should be declared local in their use.
Note:
"/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
is a way of specifying a temporary file name that will not be trampled on until after this function exits.Shared here: http://unix.stackexchange.com/a/257964/139357
Filtering stderr
This helper function will take $1 as a simple command to be used on stderr, on the rest of the command. The filtered output is emitted on stderr.
e.g. stderr "sed -e s/^/tar: /" tar -xvzf -
The function is short, but obscure, making use of a few tricks
stderr() {
{ set -- $_ "$@" ; } {_}>&1
{ eval '"${@:3}"' "$1>&-" ; } 2>&1 >&${1} | eval '$2' ">&2" "$1>&-"
set -- $1 ${PIPESTATUS[0]} "${@:2}"
eval "exec $1>&-"
return $2
}
An explanation is here:
stderr() {
{ set -- $_ "$@" ; } {_}>&1
The first line uses the temporary variable _ (underscore), which cannot generally be relied upon, but is safe enough in this context. This variable is used to avoid this helper leaving any imprint. Trampling on variables or declaring any local variables could affect destroy transparency and potentially affect the rest of the script.
So _ becomes a copy of stdout; and then inside the { ... } we update the function arguments so that this copy of stdout is now argument 1.
The function arguments are the only lexically scoped variables in bash. We can set them here in this function knowing that they will not have any other affect anywhere else.
So $1 now refers to a copy of stdout, $2 is now the filter to be applied to stderr, and "${@:3}" ($3 and onwards) is the command to be filtered.
We want to run the command with the $1 copy of standard out closed, in case the command spawns other processes that might inherit this copy and leave it open. Its a private copy, and as bash doesn't support close-on-exec we must close it.
We want to do this: "${@:3}" $1>&- but bash can't take a parameter variable on the left hand side of a redirector (not even as ${!1}) so we must use eval. We put the command in single quotes to prevent it being interpolated at all prior to eval, but the redirector is in double quotes so that the interpolated string is passed to eval; thus: eval '"${@:3}"' "$1>&-"
We want to run this command with stdout passed to the spare copy we made in $1 because we will redirect stderr to stdout to be fed into the filter. This redirection specification is: 2>&1 >&${1} (variables are allowed on the right hand side of a redirector).
However we can't append these redirectors to the previous one which already closed $1, so we use a brace scope { ... ; } in which $1 is closed.
This gives us so far: { eval '"${@:3}"' "$1>&-" ; } 2>&1 >&${1} which has stdout going to our copy of stdout, and stderr going to actual stdout ready to pipe to the next stage.
The next stage also wants to close $1 for the same reason as before, and is:
eval '$2' ">&2" "$1>&-"
So the whole invocation is:
{ eval '"${@:3}"' "$1>&-" ; } 2>&1 >&${1} | eval '$2' ">&2" "$1>&-"
We now want to close $1 for the rest of the script without losing the exit code. As we have finished calling other commands we could save $? in a local variable, but I use the function arguments again to save as $2. Note that PIPESTATUS[0] holds the result of the first stage of the pipeline.
set -- $1 ${PIPESTATUS[0]} "${@:2}"
eval "exec $1>&-"
return $2
}
And there it is.
Monday, 18 January 2016
Check status of entiire bash pipeline
pipestatus() {
local S=(${PIPESTATUS[*]})
if test -n "$*"
then test "$*" = "${S[*]}"
else ! [[ "${S[@]}" =~ [^0\ ] ]]
fi
}
Note that S is not set to ("${PIPESTATUS[@]}"); this is so that we can re-create an array if PIPESTATUS is passed as a string, like this:
PIPE_STATUS="${PIPESTATUS[*]}" pipestatus
because an array cannot be passed in that fashion. Why would anyone want to do that? Probably not directly, but other helper commands just and also may want to preserve PIPESTATUS as best as possible to permit an: also pipestatus combination.
Usage examples:
1. get_bad_things must succeed, but it should produce no output; but we want to see output that it does produce
get_bad_things | grep '^' pipeinfo 0 1 || return
2: all pipeline must succeed
thing | something -q | thingy pipeinfo || return
Thursday, 7 January 2016
Switch hdmi audio when TV is off using cec-client
When the TV is turned on, the audio plays through the TV, so that the TV remote volume control works.
When the TV is turned off, I want the audio to play through analog audio out so that I can still hear it.
I use the PulseEIGHT CEC controller and cec-client program to talk to the TV.
I have cec-client run as root under SOCAT to broadcast cec-data by UDP.
I have another SOCAT process running as the logged in user to pick up the data, monitor it for TV on/off status and move the playing audio streams.
It works, but is very rough.
In rc.local: /usr/local/bin/cec -s &
In gnome/cinnamon startup programs: /usr/local/bin/cec
And then the script:
#! /bin/bash
MYADDR=8 # because it is. I should probably read this from the cec-client output
# 0 and 1 and the audio device indexes associated with the device names
# later I will normalise these too, but "pacmd list" will show yours.
ext() {
pacmd set-default-sink alsa_output.pci-0000_00_1f.3.analog-stereo
for input in $( pacmd list-sink-inputs | sed -e 's/index: //;t;d' )
do pacmd move-sink-input $input 1
done
}
hdmi() {
pacmd set-default-sink alsa_output.pci-0000_01_00.1.hdmi-stereo-extra1
for input in $( pacmd list-sink-inputs | sed -e 's/index: //;t;d' )
do pacmd move-sink-input $input 0
done
}
cec_client() {
socat -u UDP4-RECV:4224,reuseaddr - | while read type _ _ dir rest
do # echo "$type $dir :$rest"
case "$rest" in
"0f:36") echo "POWER OFF" ; ext ;;
"(0): power status changed"*"to 'on'") echo "POWER ON" ; hdmi ;;
# *"($MYADDR) as inactive source"*) echo INACTIVE ; ext ;;
# *"($MYADDR) the active source"*) echo ACTIVATED ; hdmi ;;
# *active*) echo "$type $dir $rest" ;;
# mmkeys-mate2mpris2
esac
done
}
cec_server() {
read hostname _ < <( hostname )
hex_hostname=$( echo -n "${hostname%.*}" | od -tx1 | sed -e '1!d;s/0* //' )
<<< "tx 80 47 $hex_hostname" exec -a CEC socat -u -L/tmp/cec \
EXEC:'cec-client -t p -p 2' UDP-DATAGRAM:127.0.0.1:4224,broadcast &
wait
}
main() {
if test "$1" = "-s"
then cec_server
else cec_client
fi
}
main "$@"Monday, 2 March 2015
Packed binary in bash
I used
bc to convert the arbitrarily long decimal number into hexbc <<< "obase=16; $N"e.g.
$ N=123456789 $ bc <<< "obase=16; $N" 75BCD15I then split this into hex digit pairs to represent one byte each, in reverse order, and prefixing with
\x to produce a printf format string. I do this by appending a space to the line, and then move two (or one, if two not available) digits that precede the space to instead append to the end of the string, (with \x prefix). And then finally, remove the space which is now a leading space. sed -re 's/$/ /;:start;s/([a-fA-F0-9]{1,2}) (.*)/ \2\\x\1/;Tdone;bstart;:done;s/^ *//;'
e.g. the following pattern space is iterated 75BCD15
75BCD15␣
75BCD␣\15
75B␣\15\CD
7␣\15\CD\5B
␣\15\CD\5B\7
\15\CD\5B\7Another method of separation might have been to pad with 0 to an even length, and then split off pairs of digits from the front. I then use all of that as the argument to
printf which interpolates the characters, and then pipe to base64. printf $( bc <<< "obase=16; $N" |
sed -re 's/$/ /;:start;s/([a-fA-F0-9]{1,2}) (.*)/ \2\\x\1/;Tdone;bstart;:done;s/^ *//;'
) | base64
e.g. $ N=123456789
$ printf $( bc <<< "obase=16; $N" |
sed -re 's/$/ /;:start;s/([a-fA-F0-9]{1,2}) (.*)/ \2\\x\1/;Tdone;bstart;:done;s/^ *//;'
) | base64
Fc1bBw==
An alternate method for fixed with fields that don't need byte order fixings:Convert to hex using printf:
printf -v hex '%08x' $dec
printf '%b' "\x${hex:0:2}" "\x${hex:2:2}" "\x${hex:4:2}" "\x${hex:6:2}"
To convert a string of space separated hex to characters:input=" $input"
printf -v input '%b' ${input// /\\x}
.
Friday, 13 February 2015
Finding xterm Terminal Window Size for serial console resize
TERM=xterm), and to my frustration it can't cope when I resize the xterm, instead either garbling the output or just using the original portion.I don't know why this should be.
# shopt | grep checkwinsize
checkwinsize onVarious combinations of
# kill -SIGWINCH $$ and # kill -s WINCH $$ had no effect at all, and # stty size proved to be 0 0, and thus not very useful.Edit: It turns out that bash does not directly query the terminal size directly as a result of SIGWINCH or anything else. Changing the size of the tty with stty cols $COLUMNS rows $LINES is what causes the tty driver to send SIGWINCH to the foreground application which then queries the tty driver. Resizing an xterm is what causes xterm to modify the tty, thus sending the SIGWINCH and caused bash to take notice.With a little help from the source to xterm's resize command and also this python interpretation I came up with this 1-liner for bash, to read the xterm width and height into
LINES and COLUMNS and set these in the tty driver ready for vi or other programs to pick up:IFS=$';\x1B[' read -p $'\x1B7\x1B[r\x1B[999;999H\x1B[6n\x1B8' -d R -rst 1 _ _ LINES COLUMNS _ </dev/tty \ && stty cols "$COLUMNS" rows "$LINES"
It's worth looking at how this works.
$'...' is a bash quoted string that allows character entities to be expressed in hexadecimal, thus $'\x1B' is the character named ESC.The string emitted as a prompt instructs the xterm to save the current cursor position, move the cursor to 999,999 which should be beyond the terminal bounds and so instead move to the bottom right corner; it then instructs the xterm to report the current cursor position, and then restore the previous cursor position.
The current cursor position is returned as a string (without newline) in this form:
\x1B[lines;columnsR and so this can be read into two variables LINES and COLUMNS that bash uses. But how to do that? The bash
read function will emit the prompt, and then read the response. As the response is terminated in R instead of a newline, -d R is passed to read.Other useless characters are
\x1B [and ; so we put these into IFS causing read to use these to split the input. This gives us 2 empty strings, which we read into the bash underscore variable which gets overwritten every line anyway, so our read variable specification is _ _ LINES COLUMNS _ causing LINES and COLUMNS to take the 3rd and 4th values, with a final _ to take any potential junk that otherwise would have been appended to COLUMNS. Raw mode and silent mode are advised obviously, hence -r -s and a timeout of 1 second is set in case an xterm isn't in use and there will be no response.So why not define this as a function...
resize() {
IFS=$';\x1B[' read -p $'\x1B7\x1B[r\x1B[999;999H\x1B[6n\x1B8' \
-d R -rst 1 _ _ LINES COLUMNS _ 2>&1 &&
stty cols "$COLUMNS" rows "$LINES"
} </dev/tty >/dev/tty 2>/dev/null
The stdio redirections are to ensure that stderr is thrown away (in case set -x debug is active, which could mess this up), to ensure that the tty is being accessed whatever stdin and stdout are, and to ensure that the read command has access to stderr (for the prompt) to the tty.
Depending when/how you invoke it you may wish to not loose the previous value of
$?, saved here without the use of local variables (which through dynamic scoping might affect the called function).just() {
set -- $? "$@"
"${@:2}"
return $1
}
so now you can invoke:
just resize without affecting $?, perhaps from something ghastly like:export PS1="$PS1"'$(just resize)'
or as part of
PROMPT_COMMAND although ideally it would run before executing a command.It now remains to learn why (although a serial terminal cannot be expected to send a SIGWINCH) bash was not responding to SIGWINCH. (Note: Explained here, the signal was not to signify to basg that it should query the terminal using the methods described here, but to tell bash that the STTY rows and columns had already been changed and that it should read those. As we set those using STTY in the resize function, the signal is then sent to bash).
update: a colleague points out the a malicious terminal (or spoofed user input) could emit a bad cursor position response, so $COLUMNS and $LINES must be quoted where used to avoid some stty argument injection, or perhaps worse to other programs which might make careless use of COLUMNS or LINES without validating them as sane integers
Friday, 30 May 2014
MessageFormat {0} for bash
#! /bin/bash
# Sam Liddicott sam@liddicott.com
# e.g. formatecho "Hello {1}, date of {0} is {0,datetime}, using '{0,datetime}'\n" 1401444493 "$USER"
cutstr() {
printf -v "$1" "%s" "${4%%$3*}"
printf -v "$2" "%s" "${4#"${!1}"}"
}
formatstr() {
local target="$1"
shift
local format="$1"
shift
local result
local start
local arg
local func
# slice up to ' or { and process
while test -n "$format"
do cutstr start format "['{]" "$format"
result="$result$start"
case "$format" in
{*) cutstr start format "}" "$format"
format="${format:1}"
cutstr arg start , "${start:1}"
arg=$(( arg + 1 ))
if test "${start:0:1}" = ","
then result="$result"$(${start:1} "${!arg}")
else result="$result${!arg}"
fi
;;
"'"*) format="${format:1}"
cutstr start format "'" "$format"
# empty string means ' but bash 4.2 errors stop me defaulting
# to ' or $'\x29' so I copy the ' from format
result="$result${start:-${format:0:1}}"
format="${format:1}"
;;
esac
done
printf -v "$target" "$result"
}
formatecho() {
local _message
formatstr _message "$@"
printf "%s" "$_message"
}
datetime() {
date -d @"$@"
}
formatecho "$@"
Sunday, 20 April 2014
MAKEDEV and double virtualization
But if your virtual machine is hosting a qemu guest, then in that guest a fork/exec can take 0.2 of a second, and MAKEDEV generic-i386 can take a couple of hours. (This is because the kqemu kernel module is not available, http://www.linuxquestions.org/questions/linux-virtualization-and-cloud-90/qemu-running-on-ubuntu-vmware-guest-cannot-find-dev-kvm-936253/).
The first hacky-hack to cut down on the number of fork/exec is to stop calling sed quite so often, cue this patch (requires MAKEDEV to run under bash).
--- /sbin/MAKEDEV 2009-07-30 08:39:09.000000000 -0700
+++ /MAKEDEV 2014-02-21 06:31:43.000000000 -0800
@@ -1,4 +1,4 @@
-#! /bin/sh -
+#! /bin/bash -
# $Id$
#---#---#---#---#---#---#---#---#---#---#---#---#---#---#---#---#---#---#---#
@@ -116,7 +116,9 @@
devicename () { # translate device names to something safe
# A-Z is not full alphabet in all locales (e.g. in et_EE)
- echo "$*" | LC_ALL=C sed -e 's/[^A-Za-z0-9_]/_/g'
+ #echo "$*" | LC_ALL=C sed -e 's/[^A-Za-z0-9_]/_/g'
+ echo "${*//[^ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789]/_}"
+
}
makedev () { # usage: makedev name [bcu] major minor owner group mode
@@ -231,12 +233,14 @@
exec 3<$procfs/devices
while read major device extra <&3
do
- device=`echo $device | sed 's#/.*##'`
+ #device=`echo $device | sed 's#/.*##'`
+ device="${device%%/*}"
case "$major" in
Character|Block|'')
;;
*)
- safedevname=`devicename $device`
+ safedevname="${device//[^ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234566
+789]/_}"
eval "major_$safedevname=$major"
devices="$devices $device"
;;
@@ -247,7 +251,8 @@
Major () {
device=$2
- devname=`devicename $1`
+ devname="${1//[^ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234566
+789]/_}"
if [ "$opt_d" ]
then
echo -1 # don't care
@@ -2149,7 +2154,8 @@
exec 3<$procfs/devices
while read major device extra <&3
do
- device=`echo $device | sed 's#/.*##'`
+ # device=`echo $device | sed 's#/.*##'`
+ device="${device%%/*}"
case "$major" in
Character|Block|'')
;;
The next hack is to stop the 5 or 6 fork/exec when MAKEDEV deletes a device node, creates a (temporary device node), chmods it, chowns it, and renames it.
How do we do that? We run MAKEDEV -n so that it does none of these and then feed the output to a perl script which will do them.
Sadly perl has no native mknod call and there are no libraries in this environment, and so I use perl's blessed syscall function with a hard-wired syscall 133 for mknod (as it is, on my kernel):
time /MAKEDEV -n generic-i386 | perl -ne '
print;
umask(0);
($c, $f, $node, $major, $minor, $own, $perm) = split;
if ($node eq "->") {
symlink($major, $f) || die "sym: $!";
} else {
# system("mknod",$f,$node,$major,$minor) && die "mknod($f,$node,$major,$minor): $? $!";
# chmod(oct($perm), $f) || die "chmod: $!";
$n=0;
$n=0010000 if ($node eq "f");
$n=0020000 if ($node eq "c");
$n=0060000 if ($node eq "b");
$n=0140000 if ($node eq "s");
if (syscall(133, $f, oct($perm) | $n, (($minor & 0xff) | (($major & 0xfff) << 8)
| (( ($minor & ~0xff)) << 12)
| (( ($major & ~0xfff)) << 32))) == -1) { die "syscall: $!"; }
($user,$group)=split(/:/,$own);
$user=getpwnam($user);
$group=getgrnam($group);
chown($user,$group,$f) || die "chown: $own $!";
}
'
Leaving in the system("mknod,...) took around 24 minutes, but moving straight to syscall(mknod,...) takes1 minute 8 seconds.
I did toy with having bash use printf or something to pack a binary tar archive (or even getting perl to pack a tar archive) to pipe to tar -x, but... this will have to do for now.
I suspect this depends on a bug in MAKEDEV which seems to still create sub-directories needed even in -n mode.
It would have been better to pipe the output to a c program which would parse it, I will do that, another day...
Thursday, 12 September 2013
using sed to split a stream into 2 streams
xargs is normally the candidate for that, but what when there are multiple file types with varied actions?
Normally I would pipe into a bash scriptlet like this
... | while read "$file" ; do if [ $(expr "$file" : "$pattern" ) = "0" ] ; then ... ; else ...
but it lacks the bulk appeal of xargs which can reduce the number of command invocations by thousands of times for a large file list.
So here I make use of sed, and bash's >( ... ) construct to open a subshell and substitute a magic filename that refers a file descriptor that writes to the input of the subshell. (The substituted filename is typically something like /dev/fd/63). The newline can be entered on a terminal session with ^V ^J. It is also essential that there are no spaces between the ' and >( and also between the ) and ', otherwise the sed script will be presented to sed as multiple arguments instead of one argument.
... | sed -e '/\.ko$/{w'>( xargs strip --strip-debug )'
;d}' | xargs stripThis allows kernel objects to be stripped of debug only but other objects to be stripped entirely.
An alternative would be to use tee and a separate grep
... | tee >( grep '\.ko$' | xargs strip --strip-debug ) | grep -v '\.ko$' | xargs strip
Thursday, 31 January 2013
bash signal handling
I mean:
#! /bin/bash
trap "echo signal" 10
( sleep 1 ; kill -s 10 $$ ) &
sleep 5
We would expect the word
signal to be emitted after 1 second, but the signal handler will not run until the sleep 5 is complete.However, the built-in bash command wait does not have that problem, and so we can run the task in the background and immediately wait as in this example where the signal runs after 1 second.
#! /bin/bash
trap "echo signal" 10
( sleep 1 ; kill -s 10 $$ ) &
<&0 sleep 5 & wait
<&0 is required to cover the case that the background command needs to read stdin which would otherwise not be connected for a background command.The script also quits after 1 second even though the sleep is still running, so this variant detects if the background is still running and waits again:
#! /bin/bash
trap "echo signal" 10
( sleep 1 ; kill -s 10 $$ ) &
sleep 5 & wait
kill -s 0 $! &>- && wait
Of course this is insufficient for 2 reasons; the extra wait should be in a loop, but worse, the exit code is lost.