After some years of bash
and PowerShell
, and some hours of using fish
, I've realised that expansion & predictive typeahead are good features in a shell, whereas “be a great programming language” is less important than I thought: because there is no need to write scripts in the language of your shell.
Fish has slicker typeahead and expansions than bash or even PowerShell. But to switch to a fish
shell, you do still have to convert your profile & start-up scripts. So here's my quick-start guide for converting bash to fish.
- Do this first: at the fish prompt type
help
. Behold! the fish documentation in your browser is much easier to search than man pages are. - Calmly accept that fish uses
set var value
instead ofvar=value
. Roll your eyes if it helps. - Use
end
everywhere that bash hasfi
,done
,esac
, braces{}
etc. e.g. function definition is done withfunction ... end
. The keywordsdo
andthen
are redundant everywhere, just remove them.else
has a semicolon after it.case
requires a leadingswitch(expr)
. - There is no
[[ condition ]]
but[ ... ]
ortest ...
work. Typehelp test
to see all the file and numeric tests you expect, such asif [ -f filename ]
etc. string and regex conditionals are done with thestring match
command (see below). You can replace[[ -f this && -z that || -z other ]]
with[ -f this -a -z that -o -z other ]
but see below for how fish can also replace||
and&&
constructions withor
andand
statements. - But first! type
help string
to see the marvels of proper built-in string commands. - Replace function parameters
$*, $1, $2
etc with$argv, $argv[1], $argv[2]
etc. If that makes you scowl, then typehelp argparse
. See! That's much better than kludging about in bash. - Remove the
$
from$(subcommand)
leaving just(subcommand)
. Inside quotes, take the subcommand outside the quote:"Today is $(date)"
becomes"Today is "(date)
. (Recall that quotes in bash & fish don't work at all like quotes in most programming languages. Quote marks are not token delimiters anda"bc"d
is a valid single token and is parsed identically to each ofabcd
,"abcd"
,abc'd'
). - Replace heredocs with multi-line literal strings and standard piping syntax. However, note that if you pipe or read to a variable, the default multiline behaviour is to split on newline and generate an array. Defeat this by piping through
string split0
– see https://fishshell.com/docs/current/index.html#command-substitution
Search-and-replace Script Snippets
Here is my hit-list of things to search and replace to convert a bash shell to fish. These resolved almost all of my issues in converting a few hundred lines of bash script to fish.
From | To | Notes |
---|---|---|
var=value | set var value | |
export var=value | set -x var value | |
export -f functionname | redundant. | Just remove it |
alias abbr='commandstring' | (no change) | alias syntax is accepted as an abbreviation for a function definition since fish 3 |
command $(subshell commmand) command `subshell commmand` | command (subshell command) OR command (subshell commmand | string split0) | Just remove the $ but keep the () See below for when you want to add string split0 |
command "$(subshell commmand)" | command (subshell command) | Remove both the $ and the quotes "" to make this work |
if [[ condition ]] ; then this ; else that ; fi | if [ condition ] ; this ; else ; that ; end | See below for more on Fish's multine and and or syntax. |
if [[ number != number ]] ; then this ; else that ; fi | if [ number -ne number ] ; this ; else ; that ; end | See below for more on Fish's multine and and or syntax. |
while condition ; do something ; done | while condition ; something ; end | |
$* | $argv | |
$1, $2 | $argv[1], $argv[2] | But see help argparse |
if [[ testthis =~ substring ]] | if string match -q '*substring*' testthis | string match without -r does glob style testing |
if [[ testthis =~ regexpattern ]] | if string match -rq regexpattern testthis | string match with -r does regex testing |
[ guardcondition ] && command [ guardcondition ] || command | works as is | But see or and and below for when it's more complex |
var=${this:-$that} | if set -q this ; set var $this ; else ; set var $that ; end | |
cat > outfile <<< "heredoc" cat > outfile <<< "multiline … heredoc" | echo "multiline … heredoc" | cat > outfile | no heredocs, but multiline strings are fine NB printf is better than echo for anything complicated, in any shell. |
if [[ -z $this && $that=~$pattern ]] | if [ -z $this ] ; and string match -rq $pattern $that ; | |
content=$(curl $url) | set content (curl $url | string split0) | without the pipe to string split0 , content will be split on newlines to an array of lines. |
Fish's multine and
and or
syntax
Fish has a multiline and
and or
syntax that may be clearer than &&
and ||
in both conditionals and guarded commands. It is less terse.
[ condition ]
and do this
or do that
That said, &&
and ||
are still valid in commands :
[ condition ] && do this || do that
Other gotchas
- You may have to read up on how fish does parameter expansion, and especially handling spaces, differently to bash.
- Pipe & subcommand output to multiline strings or arrays:
set x (cat myfile.txt)
will setx
to an array of the lines ofmyfile.txt
. To keep x as a single multine string, usestring split0
:set x (cat myfile.txt | string split0)
Official tips for new fishers:
See the FAQ at https://fishshell.com/docs/3.0/faq.html