fish shell 是和 bash shell 类似的产物,它比 linux 系统默认的 bash 命令行更优胜的地方在于它的人性化,很多智能体验,或者文字样式不需要经过任何配置,就能够使用。而 zsh 之类的需要自己去配置,非常麻烦。
另一方面,fish shell 有自己的脚本语言,这个和 bash 不兼容,所以一旦你使用了 fish,那么就要考虑到这一层面的问题。很多第三方软件,都是基于 bash 的,如果在使用的时候出现什么状况,可以分析一下是不是 fish 的兼容性问题。
在日常使用中,也要学会编写 fish 特有的脚本。不过幸运的是,fish 脚本比 bash 脚本更加易于普通人理解,它和其他现代编程语言更加接近。
man set
*
: 匹配文件名的全部或者一部分**
: 匹配当前目录下的所有路径的全部或一部分|
: 左命令通过标准输出(/dev/stdout)重定向到右命令的标准输入(/dev/stdin)>
、<
: 重定向输出到右侧文件、读取右侧文件。>
左侧默认是命令的标准输出,2>
指定标准错误输出(/dev/stderr)。>&1
表示输出到标准输出。$变量名
: 读取变量值set 变量名 变量值
: 设置变量值set -e 变量名
: 撤销变量set -x 变量名 变量值
: 设置导出变量。即子脚本可以读取的变量。set -u 变量名
: 撤销导出变量set a $a b
往 $a 添加新的列表项 bcount $a
: 列表长度$a[1]
: 列表第 1 项,$a[-1]
: 列表末尾项$a[1..2]
: 列表的第 1 项到第 2 项(包括)的范围切片$a"x"$a
: 列表将和相连的内容计算笛卡尔积,如$a = 1 2 3,那么结果是:1x1 2x1 3x1 1x2 2x2 3x2 1x3 2x3 3x3。当在双引号内不会计算。set -g 变量名 变量值
: 设置全局变量,即当前脚本有效set -l 变量名 变量值
: 设置局部变量set -U 变量名 变量值
: 设置通用变量,在所有 fish 脚本内有效string join \n $变量
: 变量转化为字符串,通常是用空格连接,string join 命令可以指定连接的字符(\n 即换行)。(命令)
: 执行括号内的命令,输出作为参数内容插入,如 echo (uname)
等价于 echo Linux
set a (命令|string split0)
: 命令替换通常会根据换行来建立变量的多个表项,用 string split0
根据空字符划分表项,即最多只有一个项。命令;命令
: 在同一行可以用分号并列两个命令$status
: 上一个命令的返回值,0 为成功,非 0 为失败&&
、||
、!
、and
、or
、not
: 与,或,非,如 true && false
根据上一个命令(true)执行状态执行下一个命令(false)。与,是上个命令成功,执行下个命令,两个命令都成功,结果成功。或,是上个命令失败,执行下个命令,其中一个成功,结果成功。非,只有一个命令,如果成功,结果失败,如果失败,结果成功。and 等版本作用于上一条语句(而不是同一行的命令)。math -s0 1+1
,math 函数支持整数和小数的加(+)、减(-)、乘(x)、除(/)、幂(^)、模(%)等数学运算。-s0
保留 0 位小数,即保留整数。begin ... end
: 包含在 begin 和 end 间的语句,形成一个是局部段落,可以设置局部变量,可以将内部语句的输出一起重定向。builtin 命令
:强制使用内部命令,而不使用同名的自定义函数command 命令
:调用外部命令,而不使用内部命令和自定义函数eval "命令"
: 将参数转化为命令执行exec 命令
: 以指定命令替换掉当前的shell,当前shell关闭,即该命令永不返回。命令 (命令 | psub)
: 进程替换。命令替换和管道的结合,和普通命令替换的区别是最终以命名管道(文件)的方式作为参数输入,而不是文本程序是从上往下一次执行的,但是,如果只是顺序执行,程序就缺乏对环境变化的理解,因此任何成熟的程序语言,都带有控制流程的语句。如,判断语句,循环语句,函数等。
# 将 false 替换条件语句
if false
else if false
else
end
if ... else ... end 语句段可以添加多个 else if 判断条件 ,条件成立时执行if 和 else 之间的语句,条件不成立时,执行 else 和 end 之间的语句。也可以省略 else 段。
命令执行有成功和失败,它就是一个条件语句。
同时,fish 使用 test 命令来提供强大的判断能力。如:test "$number" -gt 5
,判断变量 number 是否大于(-gt) 5。
数字判断:
文件判断:
字符串判断:
条件组合:
# 选项分支
switch $a
case a
echo '$a == a'
case b
echo '$a == b'
case c d
echo '$a == c || $a == d'
case '*'
echo '其他,默认选项'
end
switch ...case 语句,根据内容的值($a)是否等于选项(case)来控制分支。
循环语句根据条件是否成立,重复的执行循环内部的代码。
# 死循环,不停的输出 loop
while true
echo "loop"
end
# *.txt 匹配当前目录下的所有txt后缀的文件,组成一个列表
# for file in ... 依次读取列表中的一项,保存到 $file
# more $file 显示当前文件内容
for file in *.txt
more $file
end
当遇到函数调用时,从当前流程跳转到函数内部执行。函数的作用等于将代码分割成若干功能的小片段。函数能够接受参数,并且能够返回值,很像数学上的函数概念,因此得名。
# 函数 say
# 参数 argv
function say
echo hello $argv
end
# 使用函数
say jim # 输出 hello jim
echo $status # 返回值输出 0
定义了一个函数,就可以随意调用了。之后想获得该函数的信息:
functions say # 输出该函数的定义
functions # 输出所有已经定义的函数名
函数和脚本,都可以接受参数,对参数格式的处理,可以用 argparse 函数:
function say
# 支持参数 -h --help -n -name -s --sooo
# 其中 -n jim --name ming 可以多次输入参数
# -s9 -9 --sooo=9 都是有效参数
argparse --name=myfunc 'h/help' 'n/name=+' 's#sooo' --$argv
if set -q _flag_h
echo 'say -n yourname'
echo 'say -h help'
echo 'say -9'
end
if set -q _flag_n
set -l once 1
echo -n 'hello '
for var in $_flag_n
if test $once -eq 0
echo -n ','
end
set once 0
echo -n $var
end
echo '!'
end
if set -q _flag_s
set -l i 1
echo -n "I'm S"
while test $i -le $_flag_s
echo -n 'o'
set i (math $i+1)
end
echo ' happy!'
end
end
# 使用例子
say -99 --name htqx -n xiaoming -h
函数可以处理事件。即当事件产生时,将自动调用函数。
# 处理事件
# --on-event 表示要处理的事件
function event_say --on-event say_event
echo poker face:"$argv"
end
function say
# 引发事件
emit say_event hello,$argv
end
脚本编程的一个大对象,就是针对字符串(文本)的处理。
string 命令:
echo a\nb\nc | string collect -N
echo "a\nb\nc" | escape ...
分别输出:var: a_5C_nb_5C_nc; url:a%5Cnb%5Cnc; regex:a\\nb\\nc
echo a\nb\nc | string jion ' '
:输出 a b cecho a\nb\nc | string jion0
:输出abcecho ab\nc |string pad -c x
abbr -ag ll ls -l
: 将 ls -l 用 ll 缩写代替alias ll="ls -l"
: 用 ll 函数包装 ls -l'n#max'
: -99 等cd
改变目录、cdh
通过最近访问目录列表改变目录、dirh
目录记录、prevd
上一个目录、netxd
下一个目录、pushd
入栈当前目录并进去指定目录、popd
出栈目录、pwd
显示当前目录contains key I have key
:查询列表中(I have key)是否包含指定项(key)echo -n -e “a\nb”
: 输出文本,-n 结束不换行,-e 识别转义(\n)realpath -s txt
:将相对路径转换为绝对路径,-s 保留符号链接文件变量[2]
:可以使用下标对变量的表项进行操作