语句
记住了两句话:每个语句后面都得记得打个空格,不然会编译错误!(语法规范问题)
赋值时等号两侧不能有空格,比较时等号两侧必须有空格!
条件语句
# 格式
if [[ ]] {
} elif {
} else {
}
注意: elif
不可写作 else if
。
[[ ]]
用于比较字符串、判断文件等,功能比较复杂多样,这里先使用最基础的用法。注意尽量不要用 [[ ]]
比较数值,因为不留神的话,数值会被转化成字符串来比较,没有任何错误提示,但结果可能不符合预期,导致不必要的麻烦。
# 样例
#!/usr/bin/env zsh
read str
if [[ "$str" == "name" || "$str" == "value" ]] {
echo "$str"
}else{
echo False
}
输出
1.value
value
2.suv
False
(( ))
用于比较数值,里边可以调用各种数值相关的函数,格式类似 c 语言,变量前的 $
可省略。
# 格式
if (( )) {
}
# 样例
#!/usr/bin/env zsh
read num
if ((num > 3 && num + 3 < 10)) {
echo $num
}
{ }
用于在当前 shell 运行命令并且判断运行结果。
# 格式
if { } {
}
# 样例
if {grep sd1 /etc/fstab} {
echo good
}
( )
用于在子 shell 运行命令并且判断运行结果,用法和 {} 类似。
# 格式
if ( ) {
}
这几种括号可以一起使用,这样可以同时判断字符串、数值、文件、命令结果等等。最好不要混合使用 &&
||
,会导致可读性变差和容易出错。
# 格式
if [[ ]] && (( )) && { } {
}
循环语句
while
格式
while [[ ]] {
break/continue
}
和 if
一样,这里的 [[ ]]
可以替换成其他几种括号,功能也是一样的;break
用于结束循环,continue
用于直接进入下一次循环。所有的循环语句中都可以使用 break
和 continue.
# 样例 死循环
while ((1)) {
echo good
}
until
until
和 while
相反,不满足条件时运行,一旦满足则停止,其他的用法和 while
相同。
# 格式
until [[ ]] {
}
for
for
循环主要用于枚举,这里的括号是 for
的特有用法,不是在子 shell 执行。括号内是字符串(可放多个,空格隔开)、数组(可放多个)或者哈希表(可放多个,哈希表是枚举值而不是键)。i
是用于枚举内容的变量名,变量名随意。
# 格式
for i ( ) {
}
# 样例
for i (aa bb cc) {
echo $i
}
# 枚举当前目录的 txt 文件
for i (*.txt) {
echo $i
}
# 枚举数组
array=(aa be cf)
for i ($array) {
echo $i
}
输出
aa
bb
cc
aa
be
cf
经典的 c 风格 for
循环。
# 格式
for (( ; ; )) {
}
# 样例
for ((i=0; i < 10; i++)) {
echo $i
}
这个样例只是举例,实际上多数情况不需要使用这种 for
循环,可以这样。
# 样例,{1..10} 可以生成一个 1 到 10 的数组
for i ({1..10}) {
echo $i
}
repeat
语句
repeat
语句用于循环固定次数,n
是一个整数或者内容为整数的变量。
# 格式
repeat n {
}
# 样例
repeat 5 {
echo good
}
输出
good
good
good
good
good
分支语句
# 格式 + 样例
case $i {
(a)
echo 1
;;
(b)
echo 2
# 继续执行下一个
;&
(c)
echo 3
# 继续向下匹配
;|
(c)
echo 33
;;
(d)
echo 4
;;
(*)
echo other
;;
}
;;
代表结束 case
语句,;&
代表继续执行紧接着的下一个匹配的语句(不再进行匹配),;|
代表继续往下匹配看是否有满足条件的分支。
用户输入选择语句
select
语句是用于根据用户的选择决定分支的语句,语法和 for
语句差不多,如果不 break
,会循环让用户选择。
# 格式
select i ( ) {
}
# 样例
select i (aa bb cc) {
echo $i
}
异常处理语句
# 格式
{
语句 1
} always {
语句 2
}
无论语句 1 是否出错,都执行语句 2。
#!/usr/bin/env zsh
read num
{
if (( num == 3 )) {
echo right
}
} always {
echo false
}
简化的条件语句
if
语句的简化版,在只有一个分支的情况下更简洁,功能和 if
语句类似,不赘述。
格式:
[[ ]] || {
}
[[ ]] && {
}
最好不要连续混合使用 &&
||
,比如。
aa && bb || cc && dd
容易导致逻辑错误或者误解,可以用 { }
把语句包含起来。
aa && { bb || { cc && dd } }
比较复杂的判断还是用 if
可读写更好,&&
||
通常只适用于简单的场景。
bc计算
感谢[Linux] Linux下使用bc进行计算公式_bc运算-CSDN博客 与 Linux bc 命令 | 菜鸟教程 (runoob.com)对本人在该部分学习的帮助;
常用的运算:
- + 加法
- - 减法
- * 乘法
- / 除法
- ^ 指数
- % 余数
语法
bc(选项)(参数)
选项值
- -i:强制进入交互式模式;
- -l:定义使用的标准数学库
- ; -w:对POSIX bc的扩展给出警告信息;
- -q:不打印正常的GNU bc环境信息;
- -v:显示指令版本信息;
- -h:显示指令的帮助信息。
参数
文件:指定包含计算任务的文件。
通过管道符
命令解释:
echo
命令将双引号内部的计算式作为参数 传给程序bc(类似于管道)
,然后bc
进行计算得到结果后显示输出;
或者可以不使用echo
运算符,直接将需要计算的公式字符串作为参数传给程序bc
,例如:
bc <<< "1+2"
输出为:
3
使用scale
变量设置结果小数点后的位数,例如:
sth@sth-pc:~$ echo "scale=1;3/4" | bc
.7
sth@sth-pc:~$ echo "scale=2;3/4" | bc
.75
三角函数、开方和幂次运算
bc在使用参数-l指定代数运算模式下也支持三角函数、开方、幂次等操作。
但是与matlab等语言不同的是,为了简化代码,bc中三角函数与数学上的函数名字有所不同,具体如下:
s(x) 计算 sin(x),以下x皆为弧度表示
c(x) 计算 cos(x)
a(x) 计算arctangent(x)
l(x) 计算ln(x)
e(x) 计算e的x次方,其中e为自然底数
x^y 计算x的y次方
sqrt(x) 计算根号下x
注意,进行三角函数、开方,幂次等代数运算时,一定要加上参数-l,表示进行代数运算,不然无法使用内置的三角函数、开方等函数。
进制转换
#!/bin/bash
abc=192
echo "obase=2;$abc" | bc
执行结果为:11000000,这是用bc将十进制转换成二进制。
#!/bin/bash
abc=11000000
echo "obase=10;ibase=2;$abc" | bc
执行结果为:192,这是用bc将二进制转换为十进制。
计算平方和平方根:
$ echo "10^10" | bc
10000000000
$ echo "sqrt(100)" | bc
10
编程习惯
推荐给所有变量加上花括号,这是个好的编程习惯。
已定义的变量,可以被重新定义,如:
实例
your_name="tom"
echo $your_name
your_name="alibaba"
echo $your_name
这样写是合法的,但注意,第二次赋值的时候不能写$your_name="alibaba",使用变量的时候才加美元符($)。
删除变量
使用 unset 命令可以删除变量。语法:
unset variable_name
变量被删除后不能再次使用。unset 命令不能删除只读变量。
特殊变量
有一些特殊变量在 Shell 中具有特殊含义,例如
$0 表示脚本的名称,$1, $2, 等表示脚本的参数。
$#表示传递给脚本的参数数量,$? 表示上一个命令的退出状态等。
echo命令
不加引号:字符串原样输出,变量会被替换。(根双引号一样,唯一的不同在于 \ 和 空格)
单引号:引号里面的内容会原封不动的显示出来(很简单,不做解释)
双引号:里面的特殊符号会被解析,变量也会被替换(\ 符号、空格会被解析)
反引号:用于显示命令执行结果
单双引号的优缺点
单引号字符串的限制:
- 单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的;
- 单引号字符串中不能出现单独一个的单引号(对单引号使用转义符后也不行),但可成对出现,作为字符串拼接使用。
双引号的优点:
- 双引号里可以有变量
- 双引号里可以出现转义字符
字符串操作
这部分感谢 Linux shell字符串截取、替换、删除以及trim_shell trim-CSDN博客
拼接字符串
实例
your_name="runoob"
# 使用双引号拼接
greeting="hello, "$your_name" !"
greeting_1="hello, ${your_name} !"
echo $greeting $greeting_1
# 使用单引号拼接
greeting_2='hello, '$your_name' !'
greeting_3='hello, ${your_name} !'
echo $greeting_2 $greeting_3
输出结果为:
hello, runoob ! hello, runoob !
hello, runoob ! hello, ${your_name} !
可以明显看出,不加单双引号,不会输出变量,而是会原模原样输出!
用+号符
% str1=abc
% str2=def
% str2+=$str1
% echo $str2
defabc
将两个变量拼接
s1=Hi,
s2=Jack
s=${s1}${s2} # 可以写作$s1$s2,但最好形成好的编程习惯
echo "$s" # 必须是双引号
获取字符串长度
实例
string="abcd"
echo ${#string} # 输出 4
变量为字符串时,bash中${#string} 等价于 ${#string[0]},zsh中${#string} 等价于 ${#string[1]}:
实例
string="abcd"
echo ${#string[0]} # 输出 4
截取子字符串
% str=abcdeabcde
# 删除左端匹配到的内容,最小匹配
% echo ${str#*b}
cdeabcde
# 删除右端匹配到的内容,最小匹配
% echo ${str%d*}
abcdeabc
# 删除左端匹配到的内容,最大匹配
% echo ${str##*b}
cde
# 删除右端匹配到的内容,最大匹配
% echo ${str%%d*}
abc
总结:从左至右为#,*截取字符串前面,第一个则为#,最后一个则为##;从右至左为%,第一个则为%,最后一个则为%%,*截取字符串后面。
从位置1截取到末尾
word=abcd-//master-01://httpab
echo ${word:1}
# 输出:bcd-//master-01://httpab
从位置-1截取到末尾,就是截取最后一个
word=abcd-//master-01://httpab
echo ${word:(-1)}
# 输出:b
注意:无论是bash还是zsh,第一个字符的索引值均为 0。
查找子字符串
查找字符 i 或 o 的位置(哪个字母先出现就计算哪个):
实例
string="runoob is a great site"
echo `expr index "$string" io` # 输出 4
注意: 以上脚本中 ` 是反引号,而不是单引号 ',不要看错了哦。
替换字符串
从左到右,匹配第一个,替换//为cd
#![usr/bin/env zsh
word=abcdp//hushush//httpab
echo ${word/\/\//cd}
输出:abcdpcdhushush//httpab
注意:\相当于转义符;
将所有匹配的//替换为cd
#![usr/bin/env zsh
word=abcdp//hushush//httpab
echo ${word//\/\//cd}
输出为:abcdpcdhushushcdhttpab
按内容替换和删除字符
% str=abcabc
# 只替换找到的第一个
% echo ${str/bc/ef}
aefabc
# 删除匹配到的第一个
% echo ${str/bc}
aabc
# 替换所有找到的
% echo ${str//bc/ef}
aefaef
# 删除匹配到的所有的
% echo ${str//bc}
aa
% str=abcABCabcABCabc
# /# 只从字符串开头开始匹配,${str/#abc} 也同理
% echo ${str/#abc/123}
123ABCabcABCabc
# /% 只从字符串结尾开始匹配,echo ${str/%abc} 也同理
% echo ${str/%abc/123}
abcABCabcABC123
% str=abc
总结:/:只替换或者删除找到的第一个;//: 删除或者替换所有匹配到的;
/#:只从字符串开头开始匹配,找到第一个;
/%:只从字符串结尾开始匹配,找到第一个;
# 如果匹配到了则输出空字符串
% echo ${str:#ab*}
# 如果匹配不到,则输出原字符串
% echo ${str:#ab}
abc
# 加 (M) 后效果反转
% echo ${(M)str:#ab}
按位置删除字符
%str=abcdef
# 删除指定位置字符
% str[1]=
% echo $str
bcdef
# 可以删除多个
% str[2,4]=
% echo $str
bf
跟C语言一样,对应位置写入空;
按位置替换字符
% str=abcdefg
# 一对一地替换
% str[2]=1
% echo $str
a1cdefg
# 可以多对多(也包括一对多和多对一)地替换字符,两边的字符数量不需要一致。
# 把第二、三个字符替换成 2345
% str[2,3]=2345
% echo $str
a2345defg
遍历字符
% str=abcd
% for i ({1..$#str}) {
> echo $str[i]
>}
a
b
c
d
这个就是常见的,用for循环遍历一个数组;
字符串判断
判断字符串变量是否存在
如果用 [[ "$strxx" == "" ]]
,那无法区分变量是没有定义还是内容为空,在某些情况是需要区分二者的。
% (($+strxx)) && echo good
% strxx=""
% (($+strxx)) && echo good
good
(($+var))
的用法也可以用来判断其他类型的变量,如果变量存在则返回真(0),否则返回假(1)。
字符串匹配判断
判断是否包含字符串。
% str1=abcd
% str2=bc
% [[ $str1 == *$str2* ]] && echo good
good
正则表达式匹配。(还不太清楚)
% str=abc55def
# 少量字符串的话,尽量不要用 grep
# 本文不讲正则表达式格式相关内容
# 另外 zsh 有专门的正则表达式模块
% [[ $str =~ "c[0-9]{2}\de" ]] && echo a
a
大小写转换
% str="ABCDE abcde"
# 转成大写,(U) 和 :u 两种用法效果一样
% echo ${(U)str} --- ${str:u}
ABCDE ABCDE --- ABCDE ABCDE
# 转成小写,(L) 和 :l 两种用法效果一样
% echo ${(L)str} --- ${str:l}
abcde abcde --- abcde abcde
# 转成首字母大写
% echo ${(C)str}
Abcde Abcde
总结:转成大写,(U) 和 :u 两种用法效果一样;转成小写,(L) 和 :l 两种用法效果一样;转成首字母大写,(C)。
目录文件名截取
% filepath=/a/b/c.x
# :h 是取目录名,即最后一个 / 之前的部分,如果没有 / 则为 .
% echo ${filepath:h}
/a/b
# :t 是取文件名,即最后一个 / 之后的部分,如果没有 / 则为字符串本身
% echo ${filepath:t}
c.x
# :e 是取文件扩展名,即文件名中最后一个点之后的部分,如果没有点则为空
% echo ${filepath:e}
x
# :r 是去掉末尾扩展名的路径
% echo ${filepath:r}
/a/b/c
总结::h 是取目录名,即最后一个 / 之前的部分,如果没有 / 则为 .
:t 是取文件名,即最后一个 / 之后的部分,如果没有 / 则为字符串本身
:e 是取文件扩展名,即文件名中最后一个点之后的部分,如果没有点则为空
:r 是去掉末尾扩展名的路径
字符串分隔
# 使用空格作为分隔符,多个空格也只算一个分隔符
% str='aa bb cc dd'
% echo ${str[(w)2]}
bb
% echo ${str[(w)3]}
cc
# 指定分隔符
% str='aa--bb--cc'
# 如果分隔符是 : 就用别的字符作为左右界,比如 ws.:.
% echo ${str[(ws:--:)3]}
cc
注意:使用空格作为分隔符,多个空格也只算一个分隔符;数组的索引从1开始;
# 或者先转换成数组
% str="1:2::4"
% str_array=(${(s/:/)str})
% echo $str_array
1 2 4
% echo $str_array[2]
2
% echo $str_array[3] # 124
4
# 保留其中的空字符串
% str_array=("${(@s/:/)str}")
% echo $str_array[3]
% echo $str_array[4] # 12 4
4
注意:(@s/:/)与(s/:/)的区别
多行字符串
字符串定义可以跨行。
% str="line1
> line2"
% echo $str
line1
line2
读取文件内容到字符串
# 比用 str=$(cat filename) 性能好很多
str=$(<filename)
# 比用 cat filename 性能好很多,引号不能省略
echo "$(<filename)"
# 遍历每行,引号不能省略
for i (${(f)"$(<filename)"}) {
echo $i
}
读取文件指定行。
读取进程输出到字符串
读进程输出和读文件类似。
上边字符串相关的处理,直接把 $(<test.txt)
换成 $(命令)
即可。如果一定需要一个文件名,可以这样。
# 返回 fd 路径,优先使用,但某些场景会出错
% wc -l <(ps)
4 /proc/self/fd/11
# 临时文件,会自动删除,适合上边用法出错的情况
% wc -l =(ps)
3 /tmp/zshMWDpqD