`extract` in fish
I recently switched from zsh to fish as my shell of choice. I liked the idea of starting from scratch, with the sane defaults that fish provides, as my zsh configuration files were getting a bit out of control.
For the most part, the transition was fairly painless and straightforward.
However, I still miss a few of the zsh niceties that I had been used to over the years — one being extract
function provided in oh-my-zsh.
Even after moving away from oh-my-zsh as my plugin manager in zsh, I had used my new manager to grab just that plugin for my usage.
After a cursory google, it seemed like a fish port of the plugin didn’t exist, so I decided to try to port over the plugin myself. You can find the whole function in my config.fish in my dotfiles.
First, we’ll define a function named extract
and give it a description.
I’ve also noted in a comment where this function was ported from.
function extract -d "extract files from archives"
# largely adapted from
# https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/extract
The function first checks if we have arguments — if we have none, there’s nothing to do, so we’ll echo a usage string to stderr and exit.
# no arguments, write usage
if test (count $argv) -eq 0
echo "Usage: extract [-option] [file ...] " >&2
echo " Options:" >&2
echo " -r, --remove Remove archive after unpacking." >&2
exit 1
end
Next, we check to see if the -r
/ --remove
option has been supplied as the first argument.
If it is, we’ll remove the archive files after they’ve been successfully unarchived.
We also remove this argument so that our main loop can iterate correctly over the filenames.
set remove_file 0
if test $argv[1] = "-r"; or test $argv[1] = "--remove"
set remove_file 1
set --erase argv[1]
end
Now comes the main loop, which iterates over the filenames supplied.
for i in $argv[1..-1]
First, we ensure that the filename arguments are valid files:
if test ! -f $i
echo "extract: '$i' is not a valid file" >&2
continue
end
We default our success value to 0
— if the file’s extension isn’t something we can deal with, we’ll set this to 1
so that we can avoid removing the file even if the remove option is set.
Then, we grab the extension via some fish regex matching, using the string match
function.
set success 0
set extension (string match -r ".*(\.[^\.]*)\$" $i)[2]
Now, we’ve reached the main switch statement, which is largely a translation of the zsh
version’s unarchiving calls to fish
.
switch $extension
case '*.tar.gz' '*.tgz'
tar xv; or tar zxvf "$i"
case '*.tar.bz2' '*.tbz' '*.tbz2'
tar xvjf "$i"
case '*.tar.xz' '*.txz'
tar --xz -xvf "$i"; or xzcat "$i" | tar xvf -
case '*.tar.zma' '*.tlz'
tar --lzma -xvf "$i"; or lzcat "$i" | tar xvf -
case '*.tar'
tar xvf "$i"
case '*.gz'
gunzip -k "$i"
case '*.bz2'
bunzip2 "$i"
case '*.xz'
unxz "$i"
case '*.lzma'
unlzma "$i"
case '*.z'
uncompress "$i"
case '*.zip' '*.war' '*.jar' '*.sublime-package' '*.ipsw' '*.xpi' '*.apk' '*.aar' '*.whl'
set extract_dir (string match -r "(.*)\.[^\.]*\$" $i)[2]
unzip "$i" -d $extract_dir
case '*.rar'
unrar x -ad "$i"
case '*.7z'
7za x "$i"
case '*'
echo "extract: '$i' cannot be extracted" >&2
set success 1
end
Finally, we’ll remove the original file if we’ve successfully unarchived the file, and end the loop and the function.
if test $success -eq 0; and test $remove_file -eq 1
rm $i
end
end
end
This was my first experience trying to port a larger function from zsh to fish, and it definitely took some playing around with the various test functions to get it right.
Also, the string match
functions were largely cobbled together from StackOverflow.
I strongly suggest aliasing this function to x
, or some other short sequence, for easier usage.
And voilà, we have a working general purpose extraction function, in fish!
🐠