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 valueinstead ofvar=value. Roll your eyes if it helps. - Use
endeverywhere that bash hasfi,done,esac, braces{}etc. e.g. function definition is done withfunction ... end. The keywordsdoandthenare redundant everywhere, just remove them.elsehas a semicolon after it.caserequires a leadingswitch(expr). - There is no
[[ condition ]]but[ ... ]ortest ...work. Typehelp testto see all the file and numeric tests you expect, such asif [ -f filename ]etc. string and regex conditionals are done with thestring matchcommand (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 withorandandstatements. - But first! type
help stringto see the marvels of proper built-in string commands. - Replace function parameters
$*, $1, $2etc 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"dis 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 setxto 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




