The Function Command Extension Trick
Originally published
Last modified
I'm sure it has been done before, but I haven't encountered anyone else using it in dotfiles yet, and it's worth sharing, so I hereby name this pattern the Function Command Extension Trick:
- Create a function with the same name as a command
- Handle the arguments however you like
- Execute the main command however you like, just with
command
in front (or if you are wrapping a shell builtin, putbuiltin
in front)
If you need to directly execute the main command, bypassing the wrapping function, just put command
in front.
Why?
Why not use aliases, and why name the function the same as the command it wraps?
For similar reasons that it is important to pick good variable names when programming:
- I want commands that essentially run one main command to all share that command's name as a base.
- This improves searching command history: if one command is invoked slightly differently under different names, it's more tedious to go through a linear history of how that command was used.
- My customization will be more intuitive and less foreign to people watching my screen.
gc
is a somewhat-common alias forgit commit
, but I find that people who make that alias also tend to make random garbled words for other commands that nobody intuitively knows. Real-world example found in the wild: what doesgmnffnc
mean?
Examples
I make good use of this in my git customization and cd customization.
Below is a simpler example, which adds the -T
flag when running btrfs filesystem usage
, if the output is not being piped to another program. The -T
flag outputs the same information, but using less space. Also note: btrfs accepts f
and u
as abbreviations for filesystem
and usage
.
btrfs() {
# Is stdin and stdout a terminal? (not piped)
if [[ -t 0 ]] && [[ -t 1 ]] &&
# Do the first 2 arguments start with f and u?
[[ "$1" = 'f'* ]] && [[ "$2" = 'u'* ]]; then
# Remove the first 2 arguments; "$@" becomes the rest
shift 2
# Set "$@" to the values given after --
set -- filesystem usage -T "$@"
fi
# Run the main btrfs command
command btrfs "$@"
}
Example usage:
# btrfs filesystem usage /home
Overall:
Device size: xx.xxTiB
Device allocated: xx.xxTiB
Device unallocated: x.xxTiB
Device missing: 0.00B
Used: xx.xxTiB
Free (estimated): xxx.xxGiB (min: xxxx.xxGiB)
Data ratio: 2.00
Metadata ratio: 2.00
Global reserve: 512.00MiB (used: 0.00B)
Multiple profiles: no
Data Metadata System
Id Path RAID10 RAID10 RAID10 Unallocated
-- --------- -------- -------- -------- -----------
1 /dev/sda1 x.xxTiB xx.xxGiB xx.xxMiB xxx.xxGiB
2 /dev/sdb1 x.xxTiB xx.xxGiB xx.xxMiB xxx.xxGiB
3 /dev/sdc1 x.xxTiB xx.xxGiB xx.xxMiB xxx.xxGiB
4 /dev/sdd1 x.xxTiB xx.xxGiB xx.xxMiB xxx.xxGiB
-- --------- -------- -------- -------- -----------
Total xx.xxTiB xx.xxGiB xx.xxMiB x.xxTiB
Used xx.xxTiB xx.xxGiB x.xxMiB
Example bypassing:
# command btrfs filesystem usage /home
Overall:
Device size: xx.xxTiB
Device allocated: xx.xxTiB
Device unallocated: x.xxTiB
Device missing: 0.00B
Used: xx.xxTiB
Free (estimated): xxx.xxGiB (min: xxxx.xxGiB)
Data ratio: 2.00
Metadata ratio: 2.00
Global reserve: 512.00MiB (used: 0.00B)
Multiple profiles: no
Data,RAID10: Size:xx.xxTiB, Used:xx.xxTiB (xx.xx%)
/dev/sda1 x.xxTiB
/dev/sdb1 x.xxTiB
/dev/sdc1 x.xxTiB
/dev/sdd1 x.xxTiB
Metadata,RAID10: Size:xx.xxGiB, Used:xx.xxGiB (xx.xx%)
/dev/sda1 xx.xxGiB
/dev/sdb1 xx.xxGiB
/dev/sdc1 xx.xxGiB
/dev/sdd1 xx.xxGiB
System,RAID10: Size:xx.xxMiB, Used:x.xxMiB (x.xx%)
/dev/sda1 xx.xxMiB
/dev/sdb1 xx.xxMiB
/dev/sdc1 xx.xxMiB
/dev/sdd1 xx.xxMiB
Unallocated:
/dev/sda1 xxx.xxGiB
/dev/sdb1 xxx.xxGiB
/dev/sdc1 xxx.xxGiB
/dev/sdd1 xxx.xxGiB