执行方式之间的差异
sh script
,./script
是创建一个子 shell,然后在子 shell 里面执行脚本。(所以脚本里面的变量不会在本环境生效,想起了我以前写了脚本快速进入 anaconda 环境,在脚本里面执行 source /路径/activate 结果还是没法进入环境,其实进入的是子 shell)source
在当前 shell 里面执行脚本。
shell 脚本的追踪和 debug
$ sh [-nvx] scripts.sh
选项与参数:
-n :不要执行 script,仅查询语法的问题;
-v :再执行 sccript 前,先将 scripts 的内容输出到屏幕上;
-x :将使用到的 script 内容显示到屏幕上,这是很有用的参数!
一般来说,首先先用 -n 检查是否有语法问题,然后执行有问题再用 -x 或者 -v 进行 debug
几个简单的脚本
创建一个有输入的脚本
重点是使用 read 指令,如果你想要加上提示文字请使用 -p 选项。下面 echo 的 -e 选项作用是允许转义字符(发现 macOS 的不需要直接支持)。
#!/bin/bash
read -p "Please Input Your First Name: " firstname
read -p "Please Input Your Last Name: " lastname
echo -e "\nYour Name Is $firstname $lastname"
根据日期不同创建文件名
#!/bin/bash
date=`date +"%m-%d-%Y"`
touch "Backup_$date.txt"
date 指令的使用
基本也最常用使用方法就是这样:
date +
其中格式串中的特殊字符的含义如下,用的最多的是 %Y
(年) , %m
(月) , %d
(天) ,%H
(小时),%M
(分钟),%S
(秒),其实只要记住大小写也很好记住的:
%H 小时(以00-23来表示)。
%I 小时(以01-12来表示)。
%K 小时(以0-23来表示)。
%l 小时(以0-12来表示)。
%M 分钟(以00-59来表示)。
%P AM或PM。
%r 时间(含时分秒,小时以12小时AM/PM来表示)。
%s 总秒数。起算时间为1970-01-01 00:00:00 UTC。
%S 秒(以本地的惯用法来表示)。
%T 时间(含时分秒,小时以24小时制来表示)。
%X 时间(以本地的惯用法来表示)。
%Z 市区。
%a 星期的缩写。
%A 星期的完整名称。
%b 月份英文名的缩写。
%B 月份的完整英文名称。
%c 日期与时间。只输入date指令也会显示同样的结果。
%d 日期(以01-31来表示)。
%D 日期(含年月日)。
%j 该年中的第几天。
%m 月份(以01-12来表示)。
%U 该年中的周数。
%w 该周的天数,0代表周日,1代表周一,异词类推。
%x 日期(以本地的惯用法来表示)。
%y 年份(以00-99来表示)。
%Y 年份(以四位数来表示)。
%n 在显示时,插入新的一行。
%t 在显示时,插入tab。
当然还可以使用 -d
选项指定特定的时间,下面这几种格式都是 OK 的:
date -d "1 day ago" +"%Y-%m-%d"
date -d "+1 day" +%Y%m%d #显示前一天的日期
date -d "Dec 5, 2009 12:00:37 AM 2 year ago" +"%Y-%m-%d %H:%M.%S"
date -d "2009-12-12" +"%Y/%m/%d %H:%M.%S"
条件表达式
if...elif...else...fi
基本格式如下,注意 if 和 elif 后面需要接 then,然后就是最后需要有代表 if 结束的 fi :
if [ 条件判断式一 ]; then
当条件判断式一成立时,可以进行的指令工作内容;
elif [ 条件判断式二 ]; then
当条件判断式二成立时,可以进行的指令工作内容;
else
当条件判断式一与二均不成立时,可以进行的指令工作内容;
fi
case...esac
case $变量名称 in
"第一个变量内容") <==每个变量内容建议用双引号括起来,关键词则为小括号 )
程序段
;; <==每个类别结尾使用两个连续的分号来处理!
"第二个变量内容")
程序段
;;
*) <==最后一个变量内容都会用 * 来代表所有其他值
不包含第一个变量内容与第二个变量内容的其他程序执行段
exit 1
;;
esac <==最终的 case 结尾!『反过来写』思考一下!
函数
function fname() {
程序段
}
那有人想问了,这个怎么传参呢。其实和 shell 脚本一样,通过 $1
,$2
这样的来传参,只不过和脚本不共用而已。
#!/bin/bash
function printinfo() {
echo "我是复读机: $1"
}
read -p "输入你的消息:" info
printinfo $info
循环
不定循环 while, until
while 是满足条件则进行循环。
while [ condition ] <==中括号内的状态就是判断式
do <==do 是循环的开始!
程序段落
done <==done 是循环的结束
until 是满足条件则退出循环。
until [ condition ]
do
程序段落
done
固定循环 for
for 循环第一次循环 var 等于 con1,第二次循环 var 等于 con2,以此类推。。。
for var in con1 con2 con3 ...
do
程序段
done
for 循环和 cut 指令配合使用可以处理一个文本后,然后循环其变量:
users=$(cut -d ':' -f1 /etc/passwd) # 撷取账号名称
for username in ${users} # 开始循环进行! do
id ${username}
done
假如我们想从 1 循环到 100 怎么做呢?首先一个办法是使用 seq 指令:
for var in $(seq 1 100) # seq 为 sequence(连续) 的缩写之意
do
语句
done
其次,for 也提供类似高级语言的写法:
for (( 初始值; 限制值; 执行步阶 ))
do
程序段
done
数学运算
简单的加减乘除
#!/bin/bash
read -p "Please Input First Num: " firstnum
read -p "Please Input Second Num: " secondnum
declare -i mul_ans=$firstnum*$secondnum
declare -i plus_ans=$firstnum+$secondnum
declare -i sub_ans=$firstnum-$secondnum
declare -i div_ans=$firstnum-$secondnum
echo "Multiple Ans Is : $mul_ans"
echo "Plus Ans Is : $plus_ans"
echo "Sub Ans Is : $sub_ans"
echo "Div Ans Is : $div_ans"
除了使用 declare 以外我们还可以使用 $((计算式))
来进行运算(比较推荐这一种):
mul_ans= $(($firstnum*$secondnum))
plus_ans=$(($firstnum+$secondnum))
sub_ans= $(($firstnum-$secondnum))
div_ans= $(($firstnum-$secondnum))
如果你想要计算含有小数点的数据时,应该 bc 这个指令。
bc 指令
一般在 shell 里面使用一般来说利用 echo 将表达式通过管道传给 bc 即可(-l 选项是可以数学库的函数,一般来说带上没什么影响):
echo | bc -l
重点是如何写表达式,如果是普通的加减乘除直接像下面这么写即可:
echo 'scale=2; (2.777 - 1.4744)/1' | bc
其中 scale=2 代表保留的小数点为2位。然后,bc 有一个最最最好用的东西就是他可以进行进制的转换:
$ echo "obase=2;ibase=16;FFFF" | bc
1111111111111111
obase
代表输出进制,ibase
代表输入进制。当然,还有一些常用的函数可供你使用:
函数名 | 作用 |
---|---|
s(x) | 计算 x 的正弦值,x 是弧度值。 |
c(x) | 计算 x 的余弦值,x 是弧度值。 |
a(x) | 计算 x 的反正切值,返回弧度值。 |
l(x) | 计算 x 的自然对数。 |
e(x) | 求 e 的 x 次方。 |
j(n, x) | 贝塞尔函数,计算从 n 到 x 的阶数。 |
比如,我们可以计算 pi 的值:
$ echo "scale=20;4*a(1)" | bc -l
3.14159265358979323844
强大的 expr 指令
四则运算(一般不推荐)
$ expr 3 + 5
8
$ expr 3 - 5
-2
$ expr 3 \* 5
15
$ expr 3 / 5
0
需要注意两点:
- 运算符左右都有空格,如果没有空格表示是字符串连接
- 使用乘号时,必须用反斜线屏蔽其特定含义。因为shell可能会误解显示星号的意义
字符串操作
expr length 字串
,返回字符串长度expr indexString1 String2
,返回字符串2在字符串1的第一个位置(从1开始)expr substr 内容 起始位置 终点位置
,提取字符串的子串
判断工具
test 指令
$ test expression
常用的选项:
1. 文件操作:test -e filename
-e :该文件或者目录是否存在?
-f :该文件是否存在?
-d :该目录是否存在?
2. 权限检测:test -r filename
-r, -w, -x : 检查是否有对应权限。
3. 文件比较:test file1 -nt file2
-nt, -ot :判断 file1 是否比 file2 新(老)
-ef :判断是否为同一文件(主要判断硬链接)
4. 整数比较:test n1 -eq n2
-eq, -ne :相等或者不相等
-gt, -lt :大于或者小于
-ge, -le :大于等于或者小于等于
5. 判定字符串的数据:test -z string
-z :为空返回 true
-n :非空返回 true
test str1 == str2, test str1 != str2
6. 多重条件判定:test -r filename -a -x filename
-a, -o :与和并
! : 取反
有一个操作:
test -e /home && echo "exist" || echo "Not exist"
我们可以这么理解,假设上面语句通过逻辑符号分为3个部分,只有 (1) 成功执行才会执行 (2),只有 (1) 和 (2) 都没有成功执行才会执行 (3) 。测试一下可以看到:
➜ test -e / && echo "Success and Return $?" || echo "Failed and Return $?"
Success and Return 0
➜ test -e /sdfsa && echo "Success and Return $?" || echo "Failed and Return $?"
Failed and Return 1
[ ] 符号
➜ a=1
➜ [ $a = 5 ] && echo "YES"
➜ [ $a = 1 ] && echo "YES"
YES
通过这个我们可以写一个简单的判断脚本:
#!/bin/bash
read -p "Continue or Stop? (Y/N) " choice
if [ $choice == Y ]
then
echo "Now is Continue";sleep 10s
echo "Finished"
else
echo "Stopped";exit 0
fi
带参数的脚本
其实每个 shell 对参数都设定了一些默认名称。
/path/to/scriptname opt1 opt2 opt3 opt4
$0 $1 $2 $3 $4
我写了个小小的测试,下面是我测试的脚本:
echo $0
echo $1
echo $2
测试结果如下,可以看出来 \$0 是文件名,\$1 以后的是你自己输入的变量:
➜ sh test_shell_default_var.sh hello hhh
test_shell_default_var.sh
hello
hhh
当然还有一些特殊的变量:
$# :代表后接的参数个数。
$@ :代表 ["$1" "$2" ...] 之意,每个变量是独立的(用双引号括起来)。
$* :代表 ["$1c$2c...."] ,其中 c 为分隔字符,默认为空格键。(一般来说和 $@ 没什么区别啦,直接用 $@ 就 OK)
对于上面的脚本,执行 echo $#
, echo $@
结果分别是 2
,hello hhh
同时,还有个非常好用的指令 shift,非常适合不定长的参数的处理,每次可以向前出队列一个参数。打个比方原来 $@
为 1 2 3 4
,经过一次 shift
结果 $@
变成 2 3 4
。我们可以用他来进行不定长参数的求和:
sum=0
until [ $# == 0 ]
do
sum=`expr $sum + $1`
shift
done
echo "sum is: $sum"