没有一种编程语言是完美的。甚至也没有一种最好的语言;只有一种非常合适或可能非常不合适实际目标的语言。

​ – Herbert Mayer

If it is good for everything, it is good for nothing

shell基础

前置细节准备

echo+$? 获取上个命令执行是否成功,0,成功,不为0,失败

image-20220607113456118

可以看到date成功,返回0,随便乱输入一个命令,返回值不为0

; 不具备逻辑,只是命令的排序

1
ping -c1 www.baidu.com ; echo "www.baidu.com is access" |

&& || 具备逻辑判断能力,相当于三目运算

1
2
ping -c1 www.baidu.com && echo "www.baidu.com is access" || "www.baidu.com is inaccess" 
# c1,次数为1次

这里表示,如果第一个命令执行成功(即$?==0),则执行&&后面的,否则执行||后面的

image-20220607114144700

$>/dev/null $>是混合输出(包括标准输出和错误输出)。重定向到null

比如我不想要上面命令ping的过程

1
ping -c1 www.baidu.com &>/dev/null &>/dev/null && echo "www.baidu.com is access" || "www.baidu.com is inaccess"

image-20220607114810291

上面我们是直接执行的,保存到脚本,一样的

bash和sh 执行脚本

image-20220607115222167

如果用其他命令,比如Python,会语法错误

image-20220607115347966

如果想要用绝对路径/相对路径执行,会报权限不足

image-20220607115533435

如果我们就是想要这样执行,要加权限(所以我们通常也习惯给脚本加上权限)

image-20220607115742658

#!/usr/bin/bash 第一行#!表示声明用哪个解释器执行,如果执行的试试不带解释器,则使用第一行声明的解释器执行(#不在第一行是注释)

image-20220607120437106

不同的程序用不同的解释器执行,所以我们最好在每个脚本头部都指定解释器

image-20220607120747847

EOF 外包

我们知道最好是脚本头部各找各妈,shell脚本头部就设置为bash,但是如果我就想也用上Python解释器呢?

/usr/bin/python(前面的/usr/bin/可以省略) + <<-EOF 编写Python执行的逻辑 EOF就可以做到了。

image-20220607134311282

EOF是自定义,加上-的话,结尾EOF不顶在行首也可以,不加的话,就必须顶在行首了.

image-20220607135010550

source 和 . 当前shell生效

image-20220607135927069

常规情况(bash和./)下,程序在sub shell 子shell中执行(新开的shell),你可以理解为父子进程的概念。

子shell操作完成之后,会马上退出,子shell中的变量和操作全部都会被收回,所以回到终端就看不出变化了。

比如我定义了一个别名,想要在当前shell中执行。(我希望脚本定义的东西能够影响到当前shell,就用source.执行)

image-20220607140615847

这个有多NB: 想要做什么是就请个外源来干,例子是Python,还可以是sql,expect,etc..

login shell 和 nologin shell

创建用户chen,用su chensu - chen,前者切换后没有权限,后者有权限

原因:su chen是非登录shell,没有加载登录后新用户的shell环境,su - chen是登录shell,加载登录后新用户的shell环境

image-20220607143157872

如果是登录的shell,1234都执行,不登录的shell,只执行1和4

image-20220607144429276

更多细节:

建议把这些当成习惯来用

1.命令和文件可以自动补齐

image-20220607170941905

2.命令历史记忆功能

上下键、!number、!string、!$、!!、^R

根据历史的执行id执行

image-20220607171132377

最近一个da开头的命令

image-20220607171217257

!$表示上一个命令的最后一个参数

!!表示上一个命令

^R表示搜索历史命令

3.别名

1
2
3
4
alias 查看别名
unalias cp 取消别名
~username/.bashrc
\cp -rf /etc/hosts 取消别名的作用。 这里cp在系统里的别名是 cp -i,所以拷贝第二次的时候会询问是否覆盖。反斜杠后不会询问。

4.快捷键

^代表ctrl

^R:搜索历史命令

^D: 退出 相当于exit,logout

^A: 光标到行首

^E: 光标到行尾

^L: 从新的一屏开始

^U: 从光标处往前删除

^K: 从光标处往后删除

^S: 盲打,打的命令是有在执行的!

^Q: 退出盲打

5.前后台作用控制

&: sleep 5000 & 后台执行5000s,重启进程会没了

nohup: nohup sleep 5000 & 一样的效果**,重启进程不会没**,且会生成nohup.out日志,java启动常用,终端守护进程

screen: 偏向于保护工作现场(会话)的用法

1
2
3
4
5
6
7
yum -y install screen
screen -S 会话的名字
dodododo
dodododo
会话断掉重连
screen -list
screen -r 会话的ID ==> 可以恢复会话

^C ^Z: 停止前台运行的作业

bg %1 fg %1: 去后台界面/前台界面

骚操作: 编辑文档的时候,想暂停不想退出,^Z,然后回到命令行界面,执行balabala命令,执行fg,就可以回到刚才暂停的页面了

kill %3: 一般作业%可以省略,但是 kill 3表示给pid为3的进程发信号,kill %3 给当前终端内作业号为3的发信号。代表2种情况,所以不能省略

6.输入输出重定向

0,1,2 0 b标准输入,默认键盘 1 屏幕 2 改变方向重定向

image-20220607180337766

> 输出重定向,覆盖

>> 输出重定向,追加

2> 错误输出

2>> 错误输出,追加

2>&1 描述符2的内容重定向描述符1

&> 混合输出

cat < /etc/hosts 后面可以带参数

1
这个相当于拷贝:cat </etc/hosts >/etc/hosts1

cat <<EOFEOF包裹的命令的结果传给cat

1
2
3
4
5
6
7
8
9
# 快速创建单行文件
echo "111" > file1

# 快速创建多行文件
cat <<EOF >file2
111
222
333
EOF

7.管道 | tree

一个命令的输出作为下个命令的输入

1
2
3
4
5
6
7
8
9
10
11
12
13
ip addr | grep 'inet' | grep eho0

ip addr | grep 'inet' | tree test | grep eth0 覆盖

ip addr | grep 'inet' | tree -a test | grep eth0 -a 追加

df | grep '/$'

df | tree df.txt | grep '/$'


date > date.txt # 会输入到文件
date | tree date.txt # 会输入到文件,并且创建显示内容,更直观

8.命令排序

; 不具备逻辑判断,在一行敲多个命令,用分号隔开,依次执行。不关心前面的命令执行就过,就算第一个命令失败了,第二个还是会执行

1
cd;eject # eject 是弹出光驱

&& 第一个命令返回成功(0),才会执行后面的命令(断路效果)

|| 前面的命令执行失败,才会执行后面的命令(短路效果)

1
2
3
4
5
6
./configure &7 make && make install (命令返回值echo $?)
mkdir /var/111/222/333 && echo ok # 前面失败,后面打印不会执行
mkdir -p /var/111/222/333 && echo ok # 前面成功,后面打印OK
ls /home/222/333 || mkdir -p /home/222/333 # 前面执行失败,则执行后面,创建目录

ping -c1 www.baidu.com &>/dev/null &>/dev/null && echo "www.baidu.com is access" || "www.baidu.com is inaccess" # 这2个命令配合使用组成3目运算,也就是if else的效果

9.shell通配符(元字符) 表示的不是本意

* 匹配任意多个字符

1
2
3
4
ls in*
rm -rf *
rm -rf *.pdf
find / -iname "*-eth0"

?匹配任意一个字符

1
touch love loove live l7ve;ll l?ve

image-20220607182626553

[] : 匹配括号中任意一个字符

1
2
3
4
5
6
7
8
[abc] 
[a-z]
[0-9]
[a-zA-Z0-9]
[^a-zA-Z0-9]
ll l[io]ve
ll l[^a-z]ve
ll /dev/sd[a-z]

(): 在子shell中执行

1
(cd /boot;ls)(umask 077;touch file1000)

image-20220607183449780

可以看到cd目录,不会影响到当前shell

{}: 集合

1
2
touch file{1:9}
mkdir /home/{111,222} mkdir -pv /root/script/{333/{aaa,bbb},444}

image-20220607183724845

1
2
3
4
# 大括号在拷贝中的应用
cp -rv /etc/sysconfig/network-scripts/ifcfg-eth0 /etc/sysconfig/network-scripts/ifcfg-eth0.old
cp -rv /etc/sysconfig/network-scripts/{ifcfg-eth0,ifcfg-eth0.old}
cp -rv /etc/sysconfig/network-scripts/ifcfg-eth0{,.old}

\转义符,让元字符回归本意

1
2
3
4
5
6
7
8
echo * # 打印所有文件
echo \* # 打印 * 号

mkdir \\
echo -e "atb"
echo -e "a\tb"
echo -e "anb"
echo -e "a\nb"

可以理解为把回车转义了,所以可以换行输入多行命令

image-20220607184701488

10.echo颜色输出

前景色\e[1;31m

背景色\e[1;41m

1
2
echo -e "\e[1;31mThis a red text"  # 加颜色
echo -e "\e[0m" # 恢复

image-20220607184953816

也可以使用完直接重置掉,(局部效果)

1
echo -e "\e[1;33mThis a red text.\e[0m"

image-20220607185059759

背景色

image-20220607185348573

Shell变量

shell语法…emmmmm…shell和java、Python等比没有语法难度,它只是一个工具,难的是shell结合其他软件的命令编写

程序 = 逻辑 + 数据

shell 变量是什么

比如一个脚本中,有很多次ip,我们要去一个一个改,很麻烦,就可以定义一个变量代替.修改只要修改变量的值即可。方便很多。

变量的类型

  • 自定义变量
1
2
3
4
5
定义变量: 变量名=变量值 # 变量名必须以字母或下划线开头,区分大小写 ip1=192.168.2.115
引用变量: $变量名 或 ${变量名}
查看变量: echo $变量名 set(所有变量:包括自定义变量和环境变量)
取消变量: unset 变量名
作用范围: 仅在当前shell中有效

示例:变量显式赋值

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/bash
ip=192.168.13.14
if ping -c1 $ip &>/dev/null; then
echo "$ip is up."
else
echo "$ip is down."
fi
执行结果:
[root@VM-4-13-centos scripts]# sh ping02.sh
192.168.13.14 is down.

一般我们不这么写,而是用上个命令的执行结果作为if的条件

1
2
3
4
5
6
7
8
#!/usr/bin/bash
ip=192.168.13.14
ping -c1 $ip &>/dev/null
if [ $? -eq 0 ]; then # $? 表示上个命令的返回值结果,成功0,失败!=0.方括号两边必须有空格!!!
echo "$ip is up."
else
echo "$ip is down."
fi

这里还可以用read读入ip的值,相当于java的scanner,获取键盘输入。

1
2
3
4
5
6
read -p "请输入ip值:" ip

# 输出结果:
[root@VM-4-13-centos scripts]# sh ping02.sh
请输入ip值:192.168.1.1
192.168.1.1 is down.

还可以用位置变量,获取脚本参数。$1表示执行脚本时的第一个参数,$2表示第二个,类推

image-20220612073920404

  • 环境变量
1
2
3
4
5
6
7
定义变量: 
- 方法1: export back_dir2=/home/backup
- 方法2: export back_dir1 将自定义的变量转换成环境变量
引用变量: $变量名 或 ${变量名}
查看变量: echo $变量名 env 例如: env | grep back_dir2
取消变量: unset 变量名
作用范围: 仅在当前shell和子shell中有效

不加export是自定义变量,加export的是环境变量,作用域全局,可以在子shell中获取到

image-20220612074534794

如果想要用其他文件的脚本中的变量,可以用export导出,但是一般没必要用。在用到的地方,加载执行后就可以获取到

image-20220612075036263

相当于public.sh可以是一个大项目中公共的地方.定义各种变量路径.1.sh 2.sh 引用它

  • env可以查看所有环境变量,(包括自己export的)

image-20220612075604292

/etc/profile下的PATH等,都是系统环境变量,我们可以$PATH直接使用

  • ${}
1
2
3
4
${}可以区分歧义,
比如$ip 和 $ipnew ,实际上我们想要的是变量ip,但是它会认为ipnew是变量
这时就可以用大括号隔开
${ip}new
  • 位置变量
1
$1 $2 $3 $4 $5 $6 $6 $7 $8 $8 $9 ${10}
  • 预定义变量
1
2
3
4
5
6
7
$0 脚本名
$* 所有的参数
$@ 所有的参数
$# 参数的个数
$$ 当前进程的PID
$! 上一个后台进程的PID
$? 上一个命令的返回值 0 表示成功

获取上个进程的后台进程:

image-20220612080239780

更详细的测试脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/bash
echo "第2个位置参数是$2"
echo "第1个位置参数是$1"
echo "第4个位置参数是$4"

echo "所有参数是: $*"
echo "所有参数是: $@"
echo "参数个数是: $#"

echo "当前进程PID是: $$"

echo '$1='$1
echo '$2='$2
echo '$3='$3
echo '$*='$*
echo '$@='$@
echo '$#='$#
echo '$$='$$

这里要注意下,单引号是强引用,不能方便量,会被原样输出.双引号可以放变量.如果有!要转义\!

image-20220612080909466

案例:$0 $1 $?的整合使用

定义个脚本ping07.sh,访问ip.txt中的所有ip

image-20220612081608837

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/usr/bin/bash
# 如果用户没有加参数
if [ $# -eq 0 ];then
echo "usage: `basename $0` file"
#这样写也可以,这种方式叫命令替换,反引号和$()
#echo "usage: $(basename $0) file"
fi

# 判断是文件
if [ ! -f $1 ];then
echo "error file!"
# 错误的文件,退出
exit
fi

# 循环ip
echo "ping ......"
for ip in `cat $1`
do
ping -c1 $ip &>/dev/null
if [ $? -eq 0 ];then
echo "$ip is up."
else
echo "$ip is down."
fi
done

效果:

image-20220612082739465

basename和dirname,可以获取基础路径

image-20220612081745234

变量"" 和 `` 和 $()区别

image-20220612091134821

变量运算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
方法1: expr
expr 1+2
expr $num1 + $num2
方法2:$(())
echo $(($num1+$num2))
echo $((5-3*2))
echo $(((5-3)*2))
echo $((2**3))
sum=$((1+2));echo $sum
方法3: $[]
echo $[1+1]
echo $[2**3]
方法4: let
let sum=1+2;echo $sum
let i++;echo $i

bash -vx test.sh 调试模式运行脚本

let i++应用场景:循环中变量自增,条件退出

image-20220612093719609

小数运算

1
2
3
4
5
echo "2*4" | bc
echo "2^4" | bc
echo "scale=2;6/4" | bc
awk 'BEGIN{print 1/2}'
echo "print 5.0/2" | python

变量扩展删除和替换

image-20220612100108641

image-20220615103913356

image-20220615104120820

image-20220615104202128

- 是没有值(没有被定义过)的时候,给个默认值

image-20220615104405439

:- 是没有值/空值的时候,给个默认值

image-20220615104516925

变量自增

image-20220615104737355

image-20220615105131650

条件测试

1
2
3
格式1: test 条件表达式
格式2: [ 条件表达式 ]
格式3: [[ 条件表达式 ]]

image-20220615112705782

d 目录,f 文件,L 连接

test

man test : test是条件测试的命令

image-20220615105747346

目录存在返回0,真,不存在,返回1,假

文件测试

如果目录不存在,就创建

1
2
3
4
5
6
7
8
9
#!/usr/bin/bash

back_dir=/var/mysql_back

# 测试目录是否存在
if ! test -d $back_dir;then
mkdir -p $back_dir
fi
echo "开始备份..."

测试: bash -vx test.sh

image-20220615110034695

var目录下创建了目录

image-20220615110059226

[]

注意[]也是个命令.所以也是要加空格的.

image-20220615110252170

类似于 [ 是个命令,而]是必须的参数

可以man 查看test的帮助

image-20220615111154590

还可以用type -a [观察他们的区别

image-20220615111744523

if只是做判断(后面可以跟任何语句),如果想退出脚本用exit

数值比较 eq,ne等 字符串比较 ===一个等号也可以),详细的可以在man test里面找到

[[]]

可以比正则表达式

比较是不是数字: [[ "$num" =~ ^[0-9]+$ ]]

快速创建多个用户的应用案例:

image-20220615131716368

数值比较

image-20220615114140640

image-20220615125350529

字符串比较

image-20220615125459551

字符串比较建议都加上双引号

-z判断长度是0,-n判断长度不是0

如果不加双引号,判断的时候,可能得不到想要的效果

image-20220615125920528

image-20220615130016931

脚本规范

1
2
3
4
5
6
7
8
#!/usr/bin/bash
############################
#脚本作用 #
#v1.0 by nbchen 2022/06/15 #
############################

if判断,错误的输出,一般exit 1
退出的值是任意的,可以自定义的

循环中

使用序列

1
2
3
4
5
6
7
num=10

for {1...10} // 可以

for {1...$num} // 不行

for i in `seq $num` // 正确的写法

case模式匹配

image-20220615131825647

模式就是你的值

1
2
3
4
5
6
7
8
9
while true

do

done

for i in `seq $num`
do

while循环

获取文件内容

1
2
3
while read inputstr 
do
done < user.txt

死循环:

1
2
3
4
5
6
7
8
9
while :
do
...
done

while true
do
...
done

for 循环

image-20220615152940496

等待for 循环结束

1
2
3
4
5
6
7
8
for i in 1...10
do
{
....
}&
done
wait # 等前面的所有后台进程结束,然后打印。不加的话,可能代码循环结束了,不一定执行完,所以可能会先执行下面的
echo

while util

image-20220615164123829

小结

特殊符号

image-20220615140938130

image-20220615141208246

函数

主要是为了可以重复调用

1
2
3
4
test() {
....
}
test # 调用

流程控制

image-20220615152352616

image-20220615152429132

学习demo

删除用户

if 例子

image-20220615143410605

case

image-20220615143638509

模式可以使多个,剩下的情况用*

其他命令

捕捉信号,防止ctrl+c,从脚本退出: trap “” HUP INT OUIT TSUP

多进程并发

无控制的并发

image-20220615173627673

有控制的并发

image-20220615173556388

image-20220615173607834

句柄还没有释放,文件就算删除了,也可以再拷贝回来

image-20220615174126588

image-20220615174333674

image-20220615174210791

image-20220615174218028

image-20220615174941844

小结

shell脚本编写

1
2
全链路平台报表业务复杂,SQL繁琐,导致如果直接查"逻辑SQL[处理数据的SQL]"会很慢。所以,可以通过写oracle存储过程或者是编写shell脚本的方式生成数据处理完成的结果表。这样直接查结果表,单表查询,就很快。
我们这里使用shell脚本编写,其实就是把SQL逻辑写在脚本里面,放到linux上,让他定时去跑,脚本的内容包括您的处理数据逻辑和虚拟表(数据处理完的表,类似view视图)等。

linux之if [ $? -ne 0 ];


$# 是启动脚本时携带的参数个数

-ne 是不等于

这个语句的意思是“如果shell的启动参数不等于1个”

$# 表示提供到shell脚本或者函数的参数总数;

$1 表示第一个参数。

-ne 表示 不等于

另外:

整数比较

1
2
3
4
5
6
7
8
9
10
-eq  等于,如:if ["$a" -eq "$b" ]
-ne  不等于,如:if ["$a" -ne "$b" ]
-gt  大于,如:if ["$a" -gt "$b" ]
-ge  大于等于,如:if ["$a" -ge "$b" ]
-lt  小于,如:if ["$a" -lt "$b" ]
-le  小于等于,如:if ["$a" -le "$b" ]
<    小于(需要双括号),如:(("$a" < "$b"))
<=   小于等于(需要双括号),如:(("$a" <= "$b"))
>       大于(需要双括号),如:(("$a" > "$b"))
>=      大于等于(需要双括号),如:(("$a" >= "$b"))

另外:$?是shell变量,表示”最后一次执行命令”的退出状态.0为成功,非0为失败.

linux在shell中获取时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
获得当天的日期
date +%Y-%m-%d
输出: 2011-07-28

将当前日期赋值给DATE变量
DATE=$(date +%Y%m%d)

有时候我们需要使用今天之前或者往后的日期,这时可以使用date的 -d参数
获取明天的日期
date -d next-day +%Y%m%d
获取昨天的日期
date -d last-day +%Y%m%d
获取上个月的年和月
date -d last-month +%Y%m

获取下个月的年和月
date -d next-month +%Y%m

获取明年的年份
date -d next-year +%Y

下面是一些date参数的说明和一些例子

名称 : date
使用权限 : 所有使用者
使用方式 : date [-u] [-d datestr] [-s datestr] [--utc] [--universal] [--date=datestr] [--set=datestr] [--help] [--version] [+FORMAT] [MMDDhhmm[[CC]YY][.ss]]
说明 : date 能用来显示或设定系统的日期和时间,在显示方面,使用者能设定欲显示的格式,格式设定为一个加号后接数个标记,其中可用的标记列表如下 :
时间方面 :
% : 印出
% %n : 下一行
%t : 跳格
%H : 小时(00..23)
%I : 小时(01..12)
%k : 小时(0..23)
%l : 小时(1..12)
%M : 分钟(00..59)
%p : 显示本地 AM 或 PM
%r : 直接显示时间 (12 小时制,格式为 hh:mm:ss [AP]M)
%s : 从 1970 年 1 月 1 日 00:00:00 UTC 到目前为止的秒数 %S : 秒(00..61)
%T : 直接显示时间 (24 小时制)
%X : 相当于 %H:%M:%S
%Z : 显示时区
日期方面 :
%a : 星期几 (Sun..Sat)
%A : 星期几 (Sunday..Saturday)
%b : 月份 (Jan..Dec)
%B : 月份 (January..December)
%c : 直接显示日期和时间
%d : 日 (01..31)
%D : 直接显示日期 (mm/dd/yy)
%h : 同 %b
%j : 一年中的第几天 (001..366)
%m : 月份 (01..12)
%U : 一年中的第几周 (00..53) (以 Sunday 为一周的第一天的情形)
%w : 一周中的第几天 (0..6)
%W : 一年中的第几周 (00..53) (以 Monday 为一周的第一天的情形)
%x : 直接显示日期 (mm/dd/yy)
%y : 年份的最后两位数字 (00.99)
%Y : 完整年份 (0000..9999)
若是不以加号作为开头,则表示要设定时间,而时间格式为 MMDDhhmm[[CC]YY][.ss],
其中 MM 为月份,
DD 为日,
hh 为小时,
mm 为分钟,
CC 为年份前两位数字,
YY 为年份后两位数字,
ss 为秒数
把计 :
-d datestr : 显示 datestr 中所设定的时间 (非系统时间)
--help : 显示辅助讯息
-s datestr : 将系统时间设为 datestr 中所设定的时间
-u : 显示目前的格林威治时间
--version : 显示版本编号
例子 :
显示时间后跳行,再显示目前日期 : date +%T%n%D
显示月份和日数 : date +%B %d
显示日期和设定时间(12:34:56) : date --date 12:34:56
设置系统当前时间(12:34:56):date --s 12:34:56
注意 : 当你不希望出现无意义的 0 时(比如说 1999/03/07),则能在标记中插入 - 符号,比如说 date +%-H:%-M:%-S 会把时分秒中无意义的 0 给去掉,像是原本的 08:09:04 会变为 8:9:4。另外,只有取得权限者(比如说 root)才能设定系统时间。 当你以 root 身分更改了系统时间之后,请记得以 clock -w 来将系统时间写入 CMOS 中,这样下次重新开机时系统时间才会持续抱持最新的正确值。
ntp时间同步
linux系统下默认安装了ntp服务,手动进行ntp同步如下
ntpdate ntp1.nl.net
当然,也能指定其他的ntp服务器
-------------------------------------------------------------------
扩展功能
date 工具可以完成更多的工作,不仅仅只是打印出当前的系统日期。您可以使用它来得到给定的日期究竟是星期几,并得到相对于当前日期的相对日期。了解某一天是星期几
GNU 对 date 命令的另一个扩展是 -d 选项,当您的桌上没有日历表时(UNIX 用户不需要日历表),该选项非常有用。使用这个功能强大的选项,通过将日期作为引号括起来的参数提供,您可以快速地查明一个特定的日期究竟是星期几:
$ date -d "nov 22"
Wed Nov 22 00:00:00 EST 2006
$
在本示例中,您可以看到今年的 11 月 22 日是星期三。
所以,假设在 11 月 22 日召开一个重大的会议,您可以立即了解到这一天是星期三,而这一天您将赶到驻地办公室。
获得相对日期
d 选项还可以告诉您,相对于 当前日期若干天的究竟是哪一天,从现在开始的若干天或若干星期以后,或者以前(过去)。通过将这个相对偏移使用引号括起来,作为 -d 选项的参数,就可以完成这项任务。
例如,您需要了解两星期以后的日期。如果您处于 Shell 提示符处,那么可以迅速地得到答案:
$ date -d ’2 weeks’
关于使用该命令,还有其他一些重要的方法。使用 next/last指令,您可以得到以后的星期几是哪一天:
$ date -d ’next monday’ (下周一的日期)
$ date -d next-day +%Y%m%d(明天的日期)或者:date -d tomorrow +%Y%m%d
$ date -d last-day +%Y%m%d(昨天的日期) 或者:date -d yesterday +%Y%m%d
$ date -d last-month +%Y%m(上个月是几月)
$ date -d next-month +%Y%m(下个月是几月)
使用 ago 指令,您可以得到过去的日期:
$ date -d ’30 days ago’ (30天前的日期)
您可以使用负数以得到相反的日期:
$ date -d ’dec 14 -2 weeks’ (相对:dec 14这个日期的两周前的日期)
$ date -d ’-100 days’ (100天以前的日期)
$ date -d ’50 days’(50天后的日期)
这个技巧非常有用,它可以根据将来的日期为自己设置提醒,可能是在脚本或 Shell 启动文件中,如下所示:
DAY=`date -d ’2 weeks’ +"%b %d"`
if test "`echo $DAY`" = "Aug 16"; then echo ’Product launch is now two weeks away!’; fi

##############################

unix shell中的日期格式转换
$ t_t="Jul  1 21:29"
$ date "+%G-%m-%d %H:%M:%S" -d "$t_t" # $t_t的格式是比较随意的
2008-07-01 21:29:00
$ date +%b/%d/%G -d "2008-07-01"
Jul/01/2008

unix shell中的日期之间间隔的天数
$ expr '(' $(date +%s -d "2008-07-02") - $(date +%s -d "2008-05-30") ')' / 86400
33
##存在bcdate的话,可以直接使用。

shell中日期加减指定间隔单位
增加36小时:
$ a=`date +%Y-%m-%d`
$ b=`date +%Y-%m-%d -d "$a +36 hours"`
10天前:
$ date -d "$a -10 days"
Sun Jun 22 00:00:00 CST 2008

以指定格式显示文件更改后最后日期,如yyyy-mm-dd hh24:mi:ss
$ date "+%Y-%m-%d %H:%M:%S" -r test.bak
2008-07-01 21:28:55

window下编写的shell脚本在linux执行报错

1
ps:shell脚本没有权限的话-->chmod +x xxx.sh

今天写了一个sh文件,设置好权限之后运行发现提示故障

#-bash: ./test.sh: /bin/bash^M: bad interpreter: No such file or directory

检查发现路径后面多了^M 字样,用vim打开没有,猜想应该是由于编写shell的系统为window,

用vi打开文件后用

如果出现fileforma=dos那么就基本可以确定是这个问题了。

:set ff? #出现fileforma=dos那么就基本可以确定是格式问题

解决办法:

1
2
:set fileformat=unix
:wq

没有需求,就没有脚本

前置知识

为什么学习shell

为了解决一些重复性的事情,解放你的双手,shell可以传递命令给操作系统,做很多强大的事情。

shell是什么

一门脚本语言,但是不需要编译,直接由解释器执行。其他脚本语言:Python、PHP、JavaScrip。

shell的起源

1964年,美国AT&T公司的贝尔实验室、麻省理工学院及美国通用电气公司共同参与开始研发一套可以安装在大型主机上的多用户、多任务的操作系统,该操作系统的名称为Multics。

1970年,丹尼斯•里奇和汤普逊启动了另外一个新的多用户、多任务的操作系统的项目,他们把这个项目称之为UNICS。

1973年,使用C语言重新编写了Unix。通过这次编写,使得Unix得以移植到其他的小型机上面。

1979年,第一个重要的标准UNIX Shell在Unix的第7版中推出,并以作者史蒂夫•伯恩(Stephen Bourne)的名字命名,叫做Bourne Shell,简称为sh。

20世纪70年代末,C Shell作为2BSD UNIX的一部分发布,简称csh。

之后又出现了许多其他的Shell程序,主要包括TenexCShell(tcsh)KornShell(ksh)以及GNU Bourne-Again shell(bash)。

Linux系统中比较流程的shell程序都是bash,也推荐使用标准的bash

Shell 能干嘛?(场景)

  • 自动化批量系统初始化

比如系统的update、软件安装、系统的时区设置、安全策略等

  • 自动化批量软件部署

比如常用的软件环境,一个一个命令麻烦易出错。

tomcat,mysql,nginx库,LVS,LAMP、LNMP,磁盘阵列RAID等等

  • 自动化管理程序

比如虚拟化管理(KVM),MySQL管理,远程修改密码,配置更新等

  • 自动化扩容

比如集群管理扩容(自动增加主机+部署应用/上线业务)

怎么做到的: 系统监控工具(Zabbix)监控CPU大于80%,报警通知,或者通过通过PythonAPI,调用ESC接口增删云主机。在通过Shell Script部署上线

  • 自动化日志分析处理程序

比如统计页面浏览量(PV),访问人数(UV),内存TOP状态等

  • 自动化信息采集及监控程序

比如收集系统CPU,内存(Mem),I\O负载,磁盘(Disk),网络(Net),应用状态,TCP状态等

  • 自动化备份恢复程序

比如数据库备份,增量数据同步,Crond定时调度等

  • 还可以干嘛?
    • 俄罗斯方块,坦克大战,算法,等等,shell可以做一切事情:逃)。

shell学得好可以提升你的自动化水平

shell 涉及的岗位

  • Linux运维工程师
  • DBA工程师
  • DevOps工程师
  • 软件开发工程师
  • 数据分析师
  • 架构师

脚本第一行

使用vi命令创建shell脚本文件

1
2
#!/bin/sh
# 编写逻辑代码

shell中的注释用#,并且到该行结束。#!告诉系统同一行中紧跟在它后面的那个参数是用来执行文件的程序。上面的例子中,/bin/sh作为这个程序。

设置shell可执行

脚本编辑后不能马上执行,-rw-r--r--。为了让用户拥有某个文件的执行权限,可以用chmod修改。

1
2
3
4
chmod u+x xx.sh
chmod 744 xx.sh
效果:
-rwx-r--r--

指定解释器

如果指定了解释器,则不需要给文件设置执行权限,只要有可读权限就行了。这种是开子进程执行。

1
/bin/bash xx.sh

source执行:是读取shell脚本内容,然后在当前进程执行。

1
source xx.sh

向脚本传递参数

从命令行传递给shell脚本的参数也叫位置参数,shell脚本会根据参数的位置接收它们的值。

然后,在脚本文件里可以通过一些系统变量获取这些参数。

1
2
3
4
5
6
7
8
9
10
$0 当前脚本的名称
$# 命令行传给shell脚本的参数个数,不包含$0,即排除脚本名
$n 传递给脚本的第n个参数,比如$1表示第一个参数,$2表示第二个参数......,超过9要用${}获取,比如${10}
$* 所有的参数,格式为:"参数1 参数2 参数3" , 这个返回形式是一个字符串,默认用空格隔开。(所以如果入参包含空格("b c"这样的)或者其他特殊字符,用$@获取)
$@ 所有的参数,格式为:"参数1" "参数2" "参数3" , 这个等价于$n

$_ 保存之前执行的命令的最后一个参数
$$ 当前进程的PID
$! 上一个后台进程的PID
$? 上一个命令的返回值 0 表示成功

hello world

1
2
3
#!/bin/bash
# 输出字符串
echo "hello world"

切换解释器

1
2
3
4
5
#!/usr/lcal/php5/bin/php
<?php
// 输出字符串
print "hello world"
?>

注释

1
2
3
4
5
6
7
8
# 单行注释

# 多行注释
:<<BLOCK
行1
行2
...
BLOCK

here document,将BLOCK之间的重定向到不存在的命令,间接实现了多行注释

shell 退出状态

Linux中每个命令都会返回一个退出状态码(0~255),一般成功的命令返回0,失败的返回非0.

如果没有再程序中用exit执行,则默认由最后一个命令的执行结果决定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
命令1执行
命令n执行

等价于

命令1执行
命令n执行
exit $?

等价于

命令1执行
命令n执行
exit

如果命令n报错了,则可以通过$?,获取报错后手动执行的返回状态码。

shell 配置文件

Linux中有很多类型的shell,最常用的sh和bash,他们有各自的系统环境变量的设置方法,分别保存在不同的配置文件中。

  • sh

Bourne Shell(sh)的配置文件主要有2个,分别为每个用户主目录中的.profile文件以及/etc/profile文件。后者/etc/profile是所有用户共同使用的文件。每个用户在登录shell之后,会先读取和执行/etc/profile文件中的脚本,然后再读取和执行各自主目录中的.profile文件。

可以理解为大厅(公共场所)和各自的房间(独立空间)

  • bash

Bourne-Again shell(bash)的配置文件主要有5个,有4个在用户主目录中,分别是:.bash_profile,.bashrc,.bash_logout,.bash_history,还有一个在/etc目录下,叫bashrc

.bash_profile用来保存每个用户自己使用的shell信息,当用户登录时,这个文件被读取和执行,只执行一次。一般用来设置环境变量 和 执行用户的 .bashrc 文件(文件中可以看到代码调用)。

.bashrc是属于某个用户的bash相关信息的文件。用户登录和每次打开新的bash都会执行这个文件。主要用来定义别名和函数。

.bash_logout每次用户退出shell时会执行,一般为空。

/etc/bashrc和sh中的/etc/profile非常像,任何用户登录bash后都会执行这个文件中的代码。

一般不建议修改bash的/etc/bashrc和sh的/etc/profile,如果要修改应该将修改放到用户主目录下的配置文件。

Shell基础

shell 变量

shell 条件测试

shell 判断语句

shell 循环结构

shell 函数

shell 数组

shell 正则表达式

shell 文本处理

shell 流编辑

shell 文件操作

shell 进程

shell 调试

shell 实操