Argparse Experiments

This is a test of some of the fish-shell's argparse options to see if I get how it works.

Boolean Options

The basic option definition creates a boolean option - a variable for the flag will be created if the user passes in the option and it won't be created if the user doesn't.

This example creates a short-version (-t) and a long-version (--test) for the option.

## Create the Option
set options "t/test"

## Create the Arguments
set arguments "--test"

# Parse It
argparse $options  -- $arguments

# Check if the variable was created
if set --query _flag_test
    echo "test set"
end

# If we don't define an option but check it's variable it won't exist
# this will emit the statement only if =_flag_untest= exists
if set --query _flag_untest
    echo "untested set"
end
test set

Coming from a python background this feels to me like it should raise an exception, but that's not how it works.

Passing In Values To Options

Putting an equals sign (=) tells argparse that we're (possibly) expecting a value to be passed in with the flag.

## The Options
# first a long option that's either set or not set (no values expected)
set options "test"

# now a short option that expects a value
set --append options "v="

## The Arguments
set arguments "--test" "-v 5"

## parse the arguments
argparse $options  -- $arguments

if set --query _flag_test
    echo "tested"
end

if set --query _flag_v
    echo "v=$_flag_v"
end  
tested  
v= 5

Optional Values

Missing Required Value

The prior case of creating an option with an equals sign after it creates a required value - if you use the option you have to pass in a value or it will be an error.

argparse "--value=" --  "--value"

The Result:

argparse: --value=: unknown option

Optional Value Without Equal

If you use a question mark after the option definition (-v=?) then the option expects one or no value to be passed in.

  • if you pass in more than one value it takes the first one
  • if you pass in no value it behaves like a boolean option

But, there's now a catch to passing in a value when compared to passing in a required value. Here's a case where I try to pass in an optional value.

set options "t/test" "v/value=?"

set arguments "-v 6"

argparse  -- $arguments

if set --query _flag_test
    echo "tested"
end

if set --query _flag_v
    echo "v=$_flag_v"
end

The Result:

argparse: -v 6: unknown option

This fails because the option definition v=? means the value passed in with the option (-v) is optional - so there can't be any spaces between the option and the value. To make it work the value has to be smashed up against a short-option (-v6) or passed in using an equal sign with a long option (--value=6).

Optional Values (the right way this time)

This time we'll use two versions of the optional-values syntax - ? means zero or one, * means 0 or more (it creates a list so you can pass in multiple valuse by repeating the flag).

## Defining The Options
# The number of option-values an option takes mirrors the cardinality
# symbols used with regular expressions

# A '?' means 0 or 1 value
set options "v=?"

# '*' means 0 or more
set --append options "cats=*"
set --append options "dogs=*"

## The Arguments Being Passed In
# short option values need to be smashed together with no spaces
set arguments "-v5"

# long arguments need values passed with an equal sign
set --append arguments "--cats=8"
set --append arguments "--cats=Too-Many"

# optional values are optional - passing nothing will make the flag
# act like a boolean
set --append arguments "--dogs"

argparse $options -- $arguments

if set --query _flag_v
    echo "v $_flag_v"
end

if set --query _flag_cats
    for amount in $_flag_cats
        echo "cats $amount"
    end
end

# since we defined it as taking an (optional) value
# but didn't pass in a value, the variable is created
# but there's no value in it
if set --query _flag_dogs
    echo "dogs $_flag_dogs"
end
v 5
cats 8
cats Too-Many
dogs 

Testing If A Path Argument Exists

If you put an exclamation mark (!) after the option definition it turns whatever you put after the ! into a validator to check the value passed in by the user. In this case it'll use the test command to check if the path passed in is a directory.

# here's out definition (using '+' as the cardinality (one or more)
# and a validator to check if the value passed in is a directory
set options 'p/path=+!test -d "$_flag_value"'

# a trio of paths to pass in as arguments
set arguments "--path=/tmp" "-p/home" "--p=/"

argparse $options  -- $arguments

if set --query _flag_path
    for p in $_flag_path
        echo "path: $p"
    end
end
path: /tmp
path: /home
path: /

Note that using the option multiple times creates multiple entries for the path option and that using two dashes for the short 'p' option works (weirdly) but changes it to use an equal sign instead of smashing the value up against the option (sort of a hybrid long and short option).

Also, the test checks that all the values in _flag_path are directories - if even one fails then the option isn't set.

set options 'p/path=+!test -d "$_flag_value"'

# a pair of valid paths as arguments
set arguments "--path=/tmp" "-p/home"

# and an invalid path as well
set --append arguments "--path=/oogabooga"

argparse $options  -- $arguments

if set --query _flag_path
    for p in $_flag_path
        echo "path: $p"
    end
else
    echo "No paths available."
end
No paths available.

So if you want it to be more forgiving and let some values fail you need to not test it with argparse and check the values afterwards.

Function: PDF Pages

This is a function to make it easier to extract pages from a PDF using qpdf.

The Function Declaration

function pdfpages --argument-names source_pdf target_pdf pages -d "Extract pages from a pdf"

The Command Call

This uses qpdf (GitHub Repsitory) to extract pages from a PDF. It's a function not an abbreviation because you intersperse your arguments within their syntax, rather than just pasting it all after the call.

qpdf --empty --pages $source_pdf $pages -- $target_pdf

Error Handling

if test $status -ne 0
    echo "source_pdf = $source_pdf"
    echo "target_pdf = $target_pdf"
    echo "pages = $pages"
end

Alternate Key-Bindings

Table of Contents

Alt-Backspace

Fish decided to override the Unix/emacs behavior for alt-backspace in favor of (I think) the Common-User-Access (CUA) behavior which deletes all arguments to a command instead of the previous emacs-like behavior of deleting word by word. There's an alternate keybinding but I keep forgetting about it and end up wiping out entire paths or strings when trying to correct errors so I'm putting the prior behavior back.

Here's what I'm adding to the fish configuration:

bind alt-backspace backward-kill-word

This was taken from user rski's comment, which also had some other keybindings which I'm not putting in since I don't normally use them anyway, but I'll document them here in case I decide I need them later.

bind ctrl-alt-h backward-kill-word
bind ctrl-backspace backward-kill-token
bind alt-delete kill-word
bind ctrl-delete kill-token

From Fish's bind documentation:

backward-kill-word

Move the word to the left of the cursor to the killring, until the start of the current word (like vim’s db)

backward-kill-token

Move the argument to the left of the cursor to the killring

I don't know why you'd want to set ctrl-backspace to delete one token when backspace all by itself alread does this… peculiar.

Lowercase Word

While I'm at it - the default behavior for alt-l is to do a directory listing. Besides being redundant with ls it is a little counter-intuitive if you're used to emacs with uses this keybinding to change a word to lowercase. Oddly, the default for alt-u is to change a word to uppercase like you might expect. I'm not sure why alt-l was made the way it is, but I guess I'll change it to make words lowercase.

bind alt-l "downcase-word"

downcase-word

Make the current word lowercase.

Links

PDF Functions

Table of Contents

Dump Bookmarks

This uses PDFTK to dump bookmarks (so it's not compatible with cpdf).

function pdfbookmarks -d "Dump bookmarks from a PDF (pdftk format)" --argument-names SOURCEFILE OUTFILE
    if set --query OUTFILE
        set OUTFILE "bookmarks.txt"
    end
    pdftk $SOURCEFILE dump_data_utf8 output $OUTFILE
end

See the PDF TK section of the Tealdeer post for (a little) more information.

Upgrade Function

The Function

This is a function to upgrade the various packages. It might, perhaps be better as an abbreviation…

function upgrade --description "Upgrade packages."

Bold Blue

This is a helper function to set lines (specifically headlines in this case) to be bold and blue. If this gets done enough maybe it should be a standalone function, but for now it's embedded in the upgrade function.

function boldblue
    set_color --bold blue
    echo $argv
    set_color normal
end

Apt Calls

These are calls to update the debian-based packages.

Update

These calls update the database and then show us what is upgradable.

boldblue Checking apt
sudo apt-fast update --yes
sudo apt-get --just-print upgrade

I was initially using apt to list the upgradable packages, but installing more as a pager causes its output to get piped to it, stopping progress until I quit it. Looking into how to get around it I read that apt is a front-end to other packages to make them easier to use, and that apt-get is better for non-interactive use (and is one of the things that apt is using behind the scenes anyway).

Upgrade and Autoremove

Now we run the upgrade. upgrade will upgrade installed packages and install any dependencies that are needed, but not uninstall any already installed packages. If upgrading needs to a removal it won't do the upgrade. full-upgrade will do what upgrade does but will remove any installed packages if needed to upgrade an installed package. So, we're going with full-upgrade.

I'm also auto-removing any packages that get orphaned as dependencies by an upgrade.

boldblue Upgrading apt
sudo apt-fast full-upgrade --yes
sudo apt-get autoremove --yes

Snap

Snaps should update themselves. But let's see what happens if we do it.

boldblue Updating snaps
sudo snap refresh

Flatpak

Update

boldblue Updating flatpak
flatpak update --assumeyes

type checks how a name would be interpreted (function, bultin, or file ). It returns 0 if it isn't defined. query suppresses the output.

Note: I was running this with --noninteractive but for some reason this still emitted messages that something needed to be fixed but suppressed the message telling me which package needed to be fixed, so it seems better not to use that option.

Uninstall

boldblue Uninstalling unused flatpaks
flatpak uninstall --unused --assumeyes

Some Messages

Phased Updates

The following upgrades have been deferred due to phasing:
  alsa-ucm-conf

This message is referring to Phased Updating. That is, there is an update that is being rolled out in stages to see if it is stable before it's given out to everybody. So the update exists, but you won't get it until the developers decide it's okay to roll it out to everybody.

Tealdeer Pages

Note: February 18, 2026 - tealdeer via apt is/was out of date. They changed the URL used for updating the cache (see https://github.com/tealdeer-rs/tealdeer/issues/459) so versions older than 1.8 throw an error (apt currently installs 1.7.2-1). To get 1.8 I used the Cargo Installation Instructions (which necessitated installing cargo and rust…).

This is for configuring tealdeer, a command-line help system.

  • The expected folder in this repository to put these pages is dingehaufen/tealdeer/pages/.
  • The default location that tealdeer will look for the pages is \(~/.local/share/tealdeer/pages/~\) so maybe symlink it there.
  • The pages that come with tealdeer are at \(~/.cache/tealdeer/tldr-pages/pages/\) (look here for examples)

Patches

These are patches which extend existing help pages rather than replacing them. If you want to add a new command or replace the existing tealdeer help you would use pages instead (they go in the same folder, there's just a different file-name convention).

Screen

Name the Screen

- Give the screen a name

`<Ctrl-a> A`

Ghostscript (gs)

This is for ghost script.

Black and White PDF

This is to convert a PDF's text and vector images to Black and White to make it easier to read when printed. From a Ask Ubunt (Stack Exchange) answer.

In the cases I've tried this doesn't convert raster images.

- Convert PDF text to black and white (not grayscale)

`gs -sDEVICE=pdfwrite -dBlackText -dBlackVector -dNOPAUSE -dBATCH -sOutputFile={{output-file-name}} {{pdf-input-file-name}}`

Black and White PDF and No Images

This is the previous command (Black and White) but it removes all images, leaving only vectors-based items. This is for the case where someone used a background image and all you're interested in is the text and tables.

Removes all images.

- Convert PDF text to black and white (not grayscale) and remove all images

`gs -sDEVICE=pdfwrite -dFILTERIMAGE -dBlackText -dBlackVector -dNOPAUSE -dBATCH -sOutputFile={{output-file-name}} {{pdf-input-file-name}}`

Compress For Ebook

This adds some extra flags to the compression help that comes with tealdeer.

- Reduce PDF File Size, possibly more than the version up above

`gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFSETTINGS=/ebook -dNOPAUSE -dQUIET -dBATCH -dDetectDuplicateImages -dCompressFonts=true -sOutputFile={{output-file-name}} {{input-file-name}}`
  • See : Optimizing PDFs - From the Ghostscript Blog. This notes that DetectDuplicateImages is actually on by default so you only need to use it if you want to turn it off, but I didn't read that until after using it. It might make sense to experiment with it on and off. The same might go for the CompatibilityLevel.

Convert To Grayscale

Sometimes images are RGB even though they are of black and white or gray images. Adding these flags will convert them to grayscale only. This actually doesn't save a huge amount of space most of the time (although it might if the image really was color and you're converting it tq gray) but I add the flags to the Black and White settings above to squeeze out a little more. It sometimes makes them look nicer as well.

I've only tested this with images, not vector graphics, but I think that it will strip the color from both, not just the images. Should test that out eventually…

- Convert color to grayscale

`gs -sProcessColorModel=DeviceGray -sColorConversionStrategy=Gray -sOutputFile={{output-file-name}} {{input-file-name}}`

Source: Stack Overflow - Convert PDF to grayscale without rasterization?

Resize (to Letter)

Evince no longer has the "Scale to Printable Area" option when printing PDFs, so this is something to use to get the PDF to letter-sized before printing. (from Stack Overflow: Resizing a PDF Using Ghostscript)

- Convert to Letter sized

`gs -sDEVICE=pdfwrite -dNOPAUSE -dBATCH -dCompatibilityLevel=1.4 -sPAPERSIZE=letter -dFIXEDMEDIA -dPDFFitPage -sOutputFile={{output-file-name}} {{input-file-name}}`

Virtual Fish

Check The Environment

This adds the build-state for the virtual-environments managed by virtualfish.

- List Environments and the Current State (do they need to be rebuilt?):

`vf ls --details`

Rebuild the Environment

This is the command to use after an Ubuntu upgrade where they change the python version and all the virtual environments are broken.

- Rebuild the current environment (e.g. after Ubuntu changes pip):

`vf upgrade --rebuild`

You can also rebuild a specific environment (not necessarily the one you're in).

- Rebuild a specific environment

`vf upgrade --rebuild {{virtualenv-name}}`

If the environments are broken by an update, pipx is likely broken too, run this too, maybe:

  • pipx reinstall-all
  • pip install setuptools --upgrade

pdfjam

Resize to Letter

- Resize PDF document to letter-paper size

`pdfjam {{input-file}} --paper letter --outfile {{output-file-name}}`

`pdfjam {{input-file}} --papersize 8.5in,11in --outfile {{output-file-name}}`

Emacs

Open File With Sudo

Got this from a Stack Overflow Question. The user's question is asking for an alternative way to do this, but the answers tell him that it's the best way to do it after all (he thought it was using SSH but it uses a sub-shell).

- Open a (local) file with sudo.

`C-x C-f /sudo::/path/to/file`

Use a Different Init.el

This is to use a different init.el file (presumably for debugging).

- Use a different initialization file when starting up:

`emacs --init-directory={{path/to/directory-with-init/}}`

Image Magick

Remove PDF Background

This assumes a image "density" for the background and converts the pages to images so the output file will likely be much larger. It might be best to use this for printing and then deleting the converted PDF.

- Remove background of PDF assuming the image density

`magick convert -density 300 {{path/to/input.pdf}} -white-threshold 80% {{path/to/output.pdf}}`

I also tried removing the background color specifically but doing that eats into everything that sits above the background as well (removes the anti-aliasing overlap, I assume).

PDF TK

Export Bookmarks

Export bookmarks from a PDF. I normally use cpdf for bookmarks, but in the case of Wanderhome, when I reduced the resolution to get a smaller PDF it removed the bookmarks so I had to copy them from the original and for some reason cpdf couldn't dump them, but pdftk could.

Pdftk uses a human-readable format that isn't compatible with cpdf. For Example:

BookmarkBegin
BookmarkTitle: wanderhome.pdf
BookmarkLevel: 1
BookmarkPageNumber: 0
BookmarkBegin
BookmarkTitle: Copyright
BookmarkLevel: 2
BookmarkPageNumber: 0
BookmarkBegin
BookmarkTitle: Table of Contents
BookmarkLevel: 2
BookmarkPageNumber: 0

Taken from PDFLabs Export and Import PDF Bookmarks.

- Export existing bookmarks from a pdf (UTF-8 Version)

`pdftk {{input.pdf}} dump_data_utf8 output {{bookmarks.txt}}`

Import Bookmarks

Import bookmarks dumped from another PDF. The prompt for this was that I reduced the resolution/size of a pdf and lost the bookmarks in the original and restored them by copying them over.

- Import pdftk-formatted bookmarks (from dump_data_utf8)

`pdftk {{to-update.pdf}} update_info_utf8 {{bookmarks.txt}} output {{updated.pdf}}`

exiftool

exiftool is a metadata editor. It handles many file types but I used it for PDFs.

Title

- Change the title for the file
- Note: The option needs the "=" between the name and the argument, spaces make it think you're passing in another input file.

`exiftool -Title="{{new title}}" {{input-file}}`

Git

I use git aliases (and also legit) to remember git things for me as much as possible, but this is for things that are sort of as simple as they should be but I keep forgetting about them anyway.

Checkout a File From Another Branch

- Checkout a file from another branch

`git checkout {{branch name}} {{path to file}}`

Pages

These are pages - whatever you put in a page file will be the starting point for the tealdeer entry - these will either start a new tealdeer entry or will clobber any exsting one that came with tealdeer. The thing that tells tealdeer that it's a page and not a patch is that the filename ends with page.md instead of patch.md. Otherwise the contents look the same.

Keychron K12 Keyboard

Although this is hardware, I thought it'd be helpful to put something in here in case I forget things.

Bluetooth Pairing

- Enable pairing (also needs the side-switch to be moved from USB to bluetooth). Q is QWERTY-based.

`fn1-Q (hold until LED lights up)`

MakeMKV Command Line

The makemkvcon command has a --help output, which is somewhat vague. I got the information for this from a developers text file usage.txt on makemkv.com/developers. Normally I use the GUI but sometimes it crashes due to errors specific to the GUI so the command line interface is useful to work around those cases.

List Drives

This lists the drives as makemkvcon sees them.

- List drives - DRV:index,visible,enabled,flags,drive name,disc name

`makemkvcon --robot --cache=1 info disc:9999`
Option Description
--robot Meant for automation, tells the program to output the information to the screen
--cache=1 Reduce the cache size (optional)
info The sub-command to print information
disc:9999 Show discs (not files, etc.)

The output looks something like:

MSG:1005,0,1,"MakeMKV v1.18.2 linux(x64-release) started","%1 started","MakeMKV v1.18.2 linux(x64-release)"
DRV:0,2,999,1,"DVD+R-DL HL-DT-ST DVDRAM GSA-T50N RQ01 KWF9AEJ1028","MAD_MAX_FURY_ROAD","/dev/sr0"

With more lines below it. The second line is the one I'm interested in. It has the format:

DRV:index,visible,enabled,flags,drive name,disc name

This table shows what their text file says the fields mean.

Field Description
DRV This is a drive
index The number we'll use to refer to the drive
visible 1 if drive is present
enabled 1 if drive is accessible
flags media flags (see AP_DskFsFlagXXX in apdefs.h)
drive name drive name string
disc name disc name string

Looking at the sample output, it has 2 for "visible" and 999 for "enabled", which doesn't match the expected values so I just ignore everything but the drive name to find the right entry in the output and the index for use in invoking makemkvcon.

Copy Disc As MKV Files

- Save disk to the current directory as MKV files

`makemkvcon mkv disc:{{index}} all .`

- Save disk to another directory as MKV files

`makemkvcon mkv disc:{{index}} all {{/absolute-path-to-folder/}}`

The sample shows the output being copied to c:\folder which is a DOS format (I think). It didn't seem to work when I used a relative path (other than the current directory) so I assume you need to use the full path starting from /home/.

Function: To Avif

This converts files passed in as arguments to the avif format. It requires avifenc to be installed. In Ubuntu you can install it with

sudo apt install libavif-bin

To AVIF

function toavif --description 'Convert image files to avif with avifenc'

    for SOURCE_IMAGE in $argv
        set OUTPUT (path change-extension avif $SOURCE_IMAGE)
        avifenc $SOURCE_IMAGE -o $OUTPUT
    end
end

Function: Host Grep

The Function

This is a function to check what we have set-up as a hostname for the times that I forget an IP address for a device on the LAN. It checks the hosts file and the ssh-config. The hosts file has the IP address, but sometimes I can't remember the host-name, just the user-name, in which case checking the SSH config file can sometimes be helpful.

function hostgrep --argument-names HOST --description "Look for a hostname."

The Parts

Check Hosts

First check the /etc/hosts file.

grep $HOST /etc/hosts

Check SSH

Now Check the ssh configuration.

grep --after-context 3 $HOST $HOME/.ssh/config

To Webp Functions

Table of Contents

To Webp

A simple for loop to make using globs easier with cwebp.

function towebp -d "Convert image files to web with cweb"

    for SOURCE_IMAGE in $argv
        set OUTPUT (path change-extension webp $SOURCE_IMAGE)
        cwebp $SOURCE_IMAGE -o $OUTPUT
    end
end

I'm pretty sure I saw a one-liner to do this (using pipes) but it was hard to understand so I thought this would make it clearer to myself what is going on, rather than just copying a command I can't figure out.

Lossless Webp

The previous function used the default compression (lossy). This uses the lossless conversion with maximum compression and quality.

I looked in the source code (from Google's libwebp git repository) and the -z flag is equivalent to using -lossless -m 6 -q 10 (or whatever the max is for both of them), although for some reason it seemed to do better when I was testing it earlier…

function losslesswebp --description 'Convert image files to lossless webp'

    for SOURCE_IMAGE in $argv
        set OUTPUT (path change-extension .z9.webp $SOURCE_IMAGE)
        cwebp -z 9 $SOURCE_IMAGE -o $OUTPUT
    end
end