this is a specialized instance with one use. /mnt isnāt perfect, but it was set up before we had sysadmins at the company. So basically, decision made before me.
Developers arenāt sysadmins, usually.
It wasnāt a ticket. It was a slack message with not a whole lot of information and like 30 replies. someone @ me in the thread to get my attention in the first place.
I got it sorted in about an hour of getting notified, but a good 25 minutes of that was figuring out why I couldnāt ssh into the instance in question.
I used this as an example of a situation that can be encountered in the real world, not as an example of people not following standards or an excuse for people to rage about it.
In the industry, you have to put up with things that arenāt just right and your only recourse is to change it during a change window if you really want to. Weāre so focused on other more pressing things that an instance thatās not configured perfectly in a dev environment isnāt a big deal, as long as itās not causing problems.
Thanks for the tip. Thatās going to be my life soon.
Hey, itās not all bad. You come out looking like a fucking hero every time.
And if you do it publicly enough, you get gifts from devs when you go to meetups.
work meetups shouldnt be the place to give away herpes
Oh, I was talking about whiskey, but now I know to stay away from your company.
The only times i have had to reboot pfsense have been for updates or power outages.
I havenāt seen it crash.
: ${KB:=$(( 1000))} ${KiB:=$((1<<10))}; readonly KB KiB
: ${MB:=$((1000*KB))} ${MiB:=$((1<<20))}; readonly MB MiB
: ${GB:=$((1000*MB))} ${GiB:=$((1<<30))}; readonly GB GiB
: ${TB:=$((1000*GB))} ${TiB:=$((1<<40))}; readonly TB TiB
Take your witchcraft elsewhere sir
Burn Him
What does it look like to use it?
Mind you there are probably fancier things you can do with those in bash, but even in posix shell itās fairly convenient:
example1()
{
local file=$1
local ARBITRARY_LIMIT=$(( 10 * GB ))
local file_size=$(stat -f %z $file)
if [ $file_size -gt $ARBITRARY_LIMIT ]; then
echo "That's a big file!"
else
echo "I've seen bigger."
fi
}
example2()
{
local mem=$(awk '/real memory/ { print $4 }' /var/run/dmesg.boot)
echo "$(( mem / GiB )) GiB installed memory"
}
Cool, I will definitely use that. The syntax is really weird. Iāve never seen that use of the colon, setting up the variables with the bashy :=
stuff and then declaring them. Does set -u
break it?
The reason for that (and I screwed this up in example2 even, if you caught that edit) is that readonly
will complain if you try to set the variable again, even if it is the same value.
${VAR:=VALUE}
checks if VAR is null or undefined, and sets it to VALUE if so.
Probably ${VAR=VALUE}
would be more correct (in case you need a constant for a null value or to allow a null override) because it only sets VAR if it is unset.
${parameter:=word}
Assign Default Values. If parameter is unset or null, the
expansion of word is assigned to parameter. In all cases, the
final value of parameter is substituted. Quoting inside word
does not prevent field splitting or pathname expansion. Only
variables, not positional parameters or special parameters, can
be assigned in this way.
In the parameter expansions shown previously, use of the colon in the
format results in a test for a parameter that is unset or null; omission
of the colon results in a test for a parameter that is only unset.
This pattern allows for constants to be defined in a way that doesnāt break if the script gets sourced multiple times, and it allows the default values to be overridden by the environment. Not exactly useful in the case of units, but for other constants like limits or paths or names it is handy.
$ unset TB
$ TB=1
sh: TB: is read only
$ set -u TB
$ TB=1
sh: TB: is read only
$
The :
convention is something I picked up from BSD rc scripts.
:
is shorthand for true
, which does nothing with its arguments. The parameter expansions are done by the shell, and :
ignores the resulting substituted value.
From /etc/rc.d/sshd:
: ${sshd_rsa_enable:="yes"}
: ${sshd_dsa_enable:="no"}
: ${sshd_ecdsa_enable:="yes"}
: ${sshd_ed25519_enable:="yes"}
This sets default values for the variables if they have not been explicitly set to something else in rc.conf.
Ah, I didnāt know you could throw arguments after it.
I actually donāt know if Iād ever use it. It looks pretty esoteric and all it really saves you is a few lines and some readonly
s.
It doesnāt save you any readonlys, it just happens to make readonly work because it doesnāt set if itās already set.
The main use of := is setting a default value. Compare:
: ${port:=1234}
# vs
port=${port:+1234}
# vs
[ -n "$port" ] || port=1234
run_server()
{
local HOST PORT
: ${HOST=localhost}
: ${PORT=8080}
echo "listening on $HOST:$PORT"
}
run_server
PORT=9999 run_server
listening on localhost:8080
listening on localhost:9999
I use this a lot in my scripts, for example the top of my script that creates a VM for hacking in:
: ${EXTRA_PACKAGES:="\
emacs-nox \
git \
neovim \
ripgrep \
tmux \
vim-console \
zsh \
"}
readonly EXTRA_PACKAGES
: ${BHYVE_DATASET:="storage/bhyve"}
: ${BRIDGE:="bridge0"}
: ${CPUS:="8"}
: ${NMDM_PROP:="com.freqlabs:libvirt_nmdm"}
: ${MEMORY:="16G"}
readonly BHYVE_DATASET BRIDGE CORES NMDM_PROP MEMORY
I can override things when I call the script if needed:
CPUS=4 MEMORY=4G create-work-vm
Another example from the system:
/usr/libexec/bsdinstall/zfsboot
45:: ${ZFSBOOT_POOL_NAME:=zroot}
50:: ${ZFSBOOT_POOL_CREATE_OPTIONS:=-O compress=lz4 -O atime=off}
55:: ${ZFSBOOT_BEROOT_NAME:=ROOT}
60:: ${ZFSBOOT_BOOTFS_NAME:=default}
65:: ${ZFSBOOT_VDEV_TYPE:=stripe}
70:: ${ZFSBOOT_FORCE_4K_SECTORS:=1}
81:: ${ZFSBOOT_GELI_KEY_FILE:=/boot/encryption.key}
92:: ${ZFSBOOT_BOOT_POOL_CREATE_OPTIONS:=}
97:: ${ZFSBOOT_BOOT_POOL_NAME:=bootpool}
102:: ${ZFSBOOT_BOOT_POOL_SIZE:=2g}
107:: ${ZFSBOOT_DISKS:=}
112:: ${ZFSBOOT_PARTITION_SCHEME:=}
117:: ${ZFSBOOT_BOOT_TYPE:=}
123:: ${ZFSBOOT_SWAP_SIZE:=2g}
176:: ${ZFSBOOT_CONFIRM_LAYOUT:=1}
1559: : ${ZFSBOOT_BOOT_TYPE:=UEFI}
1560: : ${ZFSBOOT_PARTITION_SCHEME:=GPT}
1567: : ${ZFSBOOT_BOOT_TYPE:=BIOS+UEFI}
1568: : ${ZFSBOOT_PARTITION_SCHEME:=GPT}
1570: : ${ZFSBOOT_BOOT_TYPE:=BIOS}
1571: : ${ZFSBOOT_PARTITION_SCHEME:=GPT}
I donāt mind those, but for static things like MB/MiB, setting default value implies it could be something else, which it never would.
Sorry Iām being a little pedantic.This is neat stuff and Iām glad you shared it. Iām always interested to learn weird bashisms.
Right the idea with combining it with readonly is to allow the script to be sourced without things breaking:
lib.sh:
: ${KB:=$(( 1000))} ${KiB:=$((1<<10))}; readonly KB KiB
: ${MB:=$((1000*KB))} ${MiB:=$((1<<20))}; readonly MB MiB
: ${GB:=$((1000*MB))} ${GiB:=$((1<<30))}; readonly GB GiB
: ${TB:=$((1000*GB))} ${TiB:=$((1<<40))}; readonly TB TiB
$ . lib.sh
# now maybe I decide to edit the script to add some more things
# I can source it again to update my env
$ . lib.sh
In contrast this would break:
readonly KB=$(( 1000)) KiB=$((1<<10))
readonly MB=$((1000*KB)) MiB=$((1<<20))
readonly GB=$((1000*MB)) GiB=$((1<<30))
readonly TB=$((1000*GB)) TiB=$((1<<40))
$ . lib.sh
$ . lib.sh
readonly: KB: is read only
It makes it safe to set these in a .profile or to source in other scripts that might be sourced.
Youād be surprised at how many all day issues stem from something minor that was overlooked. Even sysadmins come across this in their own setups no matter how well it is documented. The only thing that comes after is how much self-loathing and alcohol is involved with such a simple fix, yet took soo long to figure out.