fish shell tutorial

June 4, 2016
Tools

在fish里面,相邻的列表或者字符串会按照笛卡尔积来展开。比如:

liu@lisp /p/tmp> echo {good,bad}" apples"
good apples bad apples

liu@lisp /p/tmp> set -l a x y z
liu@lisp /p/tmp> echo $a 
x y z
liu@lisp /p/tmp> set -l b 1 2 3

liu@lisp /p/tmp> echo $a$b
x1 y1 z1 x2 y2 z2 x3 y3 z3

liu@lisp /p/tmp> echo $a"-"$b
x-1 y-1 z-1 x-2 y-2 z-2 x-3 y-3 z-3

liu@lisp /p/tmp> echo {x,y,z}$b
x1 y1 z1 x2 y2 z2 x3 y3 z3

liu@lisp /p/tmp> echo {$b}word
1word 2word 3word

liu@lisp /p/tmp> echo "$b"word
1 2 3word

上面用到了括号扩展的功能:在一个大括号里面以逗号分隔的几个字符串会被扩展成列表里 面的每个元素被当作一个新的变量。比如:

liu@lisp /p/tmp> echo input.{c,h,txt} 
input.c input.h input.txt
liu@lisp /p/tmp> mkdir src 

liu@lisp /p/tmp> touch  input.{c,h,txt} 
liu@lisp /p/tmp> mv *.{c,h} src 
liu@lisp /p/tmp> ls src 
input.c input.h

在其它shell里面,一般都使用\`来作为命令替换,在fish里面,就不一样了。它使用的 是括号。比如:

liu@lisp /p/tmp> echo In (pwd), running (uname) 
In /private/tmp, running Darwin
liu@lisp /p/tmp> set os (uname)
liu@lisp /p/tmp> echo $os 
Darwin

其实这种方法变得更简单直接。不过,需要注意的是,这种方法不能使用在双引号里面,它 并不会在双引号里面自动把命令替换成命令的输出结果。弥补的做法只能是这样:

liu@lisp /p/tmp> touch "testing_"(date +%s)".txt"
liu@lisp /p/tmp> ls te* 
testing_1465007369.txt

也就是说拼接一下就好了。

另外,一个经常用到的功能就是,不像其它shell那样使用&&||来作为逻辑与和逻辑 或,fish里面用的是英语单词and, or, not,比如:

liu@lisp /p/tmp> touch file1.txt 
liu@lisp /p/tmp> cp file1.txt file1_bak.txt; and echo "Backup successful"; or echo "Backup failed" 
Backup successful

liu@lisp /p/tmp> cp file2.txt file1_bak.txt; and echo "Backup successful"; or echo "Backup failed" 
cp: file2.txt: No such file or directory
Backup failed

然而,中间还是要加分号分割。

经常用到的,条件语句这样使用:

liu@lisp /p/tmp> if grep fish /etc/shells
                     echo Found fish
                 else if grep bash /etc/shells
                     echo Found bash 
                 else 
                     echo Got nothing
                 end
/usr/local/bin/fish
Found fish

显然比其它的shell的语法自然好看多了 :joy: 即使你在终端里面直接输入这些代码,它也 会自动锁进。

同样有简单的switch语句:

liu@lisp /p/tmp> switch (uname)
                     case Linux
                         echo Hi Linux!
                     case Darwin
                         echo Hi Mac OS X!
                     case FreeBSD NetBSD
                         echo Hi BSD
                     case '*'
                         echo Hi, stranger!
                 end
Hi Mac OS X!

不像其它的shell一样使用alias和用特殊的提示符语法之类的……这些事情,在fish里面用函 数替换,变得更简单好用。

函数的参数是放在变量$argv里面的,而不是像别的shell那样放在奇怪的$1里面:

liu@lisp /p/tmp> function say_hello
                     echo Hello $argv
                 end
liu@lisp /p/tmp> say_hello 
Hello
liu@lisp /p/tmp> say_hello everyone!
Hello everyone!

functions这个关键字,可以列出所有的函数。有趣的是在这个关键字后面加一个函数的 名字可以看到这个函数的源代码:

liu@lisp /p/tmp> functions
., N_, abbr, alias, cd, contains_seq, delete-or-exit, dirh, dirs,
down-or-search, eval, export, fish_config, fish_default_key_bindings,
fish_fallback_prompt, fish_indent, fish_mode_prompt, fish_prompt,
fish_sigtrap_handler, fish_update_completions, fish_vi_cursor,
fish_vi_key_bindings, fish_vi_mode, fisher, funced, funcsave, grep, help,
history, hostname, imgcat, isatty, it2dl, iterm2_fish_mode_prompt,
iterm2_fish_prompt, iterm2_precmd, iterm2_preexec, iterm2_prompt_end,
iterm2_prompt_start, iterm2_set_user_var, iterm2_status, la, ll, ls, man,
math, nextd, nextd-or-forward-word, open, popd, prevd, prevd-or-backward-word,
prompt_pwd, psub, pushd, realpath, say_hello, seq, setenv, suspend, trap,
type, umask, underscore_change, up-or-search, vared,
liu@lisp /p/tmp> functions fish_vi_mode 
function fish_vi_mode
	echo 'The `fish_vi_mode` function is deprecated.' >&2
        echo 'Please switch to calling `fish_vi_key_bindings`.' >&2
	# Turn on vi keybindings
	set -g fish_key_bindings fish_vi_key_bindings
end

for循环也很简单直接:

liu@lisp /p/tmp> for file in *.txt 
                     cp $file $file.bak
                 end
liu@lisp /p/tmp> ls *.txt; ls *.bak 
errors.txt             file1_bak.txt          testing_1465007369.txt
file1.txt              input.txt
errors.txt.bak             input.txt.bak
file1.txt.bak              testing_1465007369.txt.bak
file1_bak.txt.bak

seq 这个命令加一个数字n可以生成从1到n的一个列表,比如:

liu@lisp /p/tmp> seq 5 
1
2
3
4
5
liu@lisp /p/tmp> for x in (seq 5)
                     touch file_$x.txt
                 end
liu@lisp /p/tmp> ls file_* 
file_1.txt file_2.txt file_3.txt file_4.txt file_5.txt

在别的shell里面,一般用$PS1这个变量来显示提示符,在fish里面,使用的是一个名字 叫做fish_prompt的函数,这个函数的输出就作为提示符,比如,你可以像下面这样定制 自己的提示符:

liu@lisp /p/tmp> function fish_prompt
                     echo "Sabastian Bach >> "
                 end
Sabastian Bach >>

可以通过set_color来设置颜色,可以把ANSI colors或者RGB的16进制的值 作为参数:

Sabastian Bach >> function fish_prompt 
                      set_color purple
                      date "+%m/%d/%y"
                      set_color FF0
                      echo (pwd) '> '
                      set_color normal
                  end
06/04/16
/private/tmp > 

fish里面有一种全局变量,这个变量在fish里面通用,只要设置一次,那么即使重启,这个 变量依然存在。比如这样设置EDITOR这个变量:

 fish $ set -U EDITOR vim
 fish $ echo $EDITOR                                                        
vim

打开另外一个终端这个变量依然有效。

另外有意思的一点是,其它shell里面,你一般会在类似于~/.bashrc或者~/.zshrc里面 设置$PATH的值,当然,在fish里面,你也可以这样做。比如你可以在 ~/.config/fish/config.fish这个文件里面设置。如果没有这个文件就需要创建一个。其 实它就相当于bash里面的~/.bashrc。你可以在这个文件里面定义一些函数,或者设置一 些变量。比如:

> cat ~/.config/fish/config.fish

set -x PATH $PATH /sbin/

function ll
    ls -lh $argv
end

然而,在fish里面更高效和更常见的做法是,使用自动加载函数和上面提到的全局变量。

所谓自动加载函数,就是在~/.config/fish/functions/目录里面以命令的名字加上后缀 .fish作为文件名新建一个文件。比如你可以定义你自己的提示符:

> cat ~/.config/fish/functions/fish_prompt.fish
function fish_prompt
    echo (pwd) "> "
end

其实函数对应的文件可以自动创建,参见 funcedfuncsave

为了说明这个用法,比如我在终端直接输入:

 Pictures $ function gopi
                ssh aries@pi
            end
 Pictures $ funcsave gopi

然后以后就可以使用gopi这个命令了……是不是很简单。根本不用打开一个什么文件什么的。 (这个命令是用来登陆我的树莓派的…… :joy: )

fish有一个插件特别好用,类似于zsh的oh-my-zsh。Github地址是:fisherman

安装的时候,可能会因为git.io被墙安装失败。其实,只要把git.io/fisher里面的内 容复制到~/.config/fish/functions/fisher.fish文件里面就可以了。没有这个文件就新 建一个。

然后就可以愉快地使用了。

经常会遇到设置环境变量的情况,可以用paths 这个插件愉快地搞定。比如我给$PATH追加一个变量,可以这样做:

mkdir $paths_config/PATH
echo ~/bin > $paths_config/PATH/HOMEBIN

然后重新启动fish才有效。这一点很重要!

总的来说,仿佛fish和bash、zsh之类的差别很大,然而用过之后才知道,其实它变得更简 单并且,更好配置,去掉了别的shell里面那些乱七八糟奇怪的语法,符合更常用的语言的 语法规则,所以,我所有的*nix环境都换成了fish :joy: 棒棒哒


日常锻炼

6月1日,5.27km

坚持这件事情,与其说靠的是毅力,不如说是惯性。人的毅力靠不住,习惯却很顽固。

6月2日,5.26km

今天跑步听的是友聊FM最新一期《闲聊飞了》,聊的是小米无人机,同样,不知不觉,就跑 完了……

6月3日,5.21km

最近看周易,跑步的时候默背分宫卦象图 :joy: 我是有……多无聊。。。

6月4日,5.17km

Android版的Google拼音输入法比搜狗好用并且好看多了……

comments powered by Disqus