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:

  1. Create a function with the same name as a command
  2. Handle the arguments however you like
  3. Execute the main command however you like, just with command in front (or if you are wrapping a shell builtin, put builtin 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 for git 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 does gmnffnc 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