文章目录
一、COM
shell不算是高级语言,是面向过程的,但其目的本身就不是为了大型项目,而仅仅是以命令行的形式操作linux,其实就是多个linux命令的汇总,类似于宏。
Shell(外壳)跟kernel(内核)相互对应,意为跟内核交互的外层。
Shell本身是一个程序,它提供了一个与用户交互的环境。这个环境只有一个命令提示符,用于接收用户输入命令,再将命令提交给操作系统执行,最后将结果返回给用户,所以又称为命令行环境(commandline,CLI)。
Shell也是一个命令解释器,可以解释执行用户输入的命令(shell脚本)。
要理解shell,先要明确几个认知,
-
任何程序都不能直接操作计算机,这里的计算机是狭义的计算机硬件,只有kernel才能直接操作,而人显然不能去操作kernel,所以需要一个中介,这个中介就是shell,中介显然有很多种,比如java程序、python程序,shell也有很多种:bash、dash。
- 都说Shell可以直接与计算机交互,但是我想其他语言不也能方便的与计算机进行交互吗?
不是shell可以和计算机交互,是用户通过shell与OS交互。其他语言编译完,就等待用户运行,可是怎么让OS知道用户要运行哪一个呢?参数是什么呢?这就需要shell了。从角色上看,windows系统的explorer程序也是一种shell,只不过是图形的,用户通过点击与OS完成交互。所以,shell的最重要的作用就是把用户的命令交给OS,和一般的计算机语言不在同一层。
- 都说Shell可以直接与计算机交互,但是我想其他语言不也能方便的与计算机进行交互吗?
-
linux的命令其实都是函数,比如mkdir,就是用c写的一个函数
而Shell 的优势就是能轻易调用所有用其他语言编写的程序,比如可以在一个shell里调用用c写的mkdir,用java -jar调用java程序。当然java里面也可以调用mkdir,但显然需要多一些封装。
==》
shell,就是个命令行式的函数调用器。windows的各种图形化操作其实都是shell操作。shell可不仅仅是命令行。而是中介和桥梁。
二、命令提示符
[userName@hostname dir] $
username:指用户名
hostname:指主机名
dir:指当前目录,~
表示用户主目录
KaTeX parse error: Expected 'EOF', got '#' at position 16: :用户提示符,root用户为`#̲`,非root用户为``
shell有很多有,比如bash和sh
切换shell
如果当前运行的不是bash,可以通过命令切换
# 当前登录使用的shell是sh,命令提示符为:sh-4.2$
# 通过bash命令即可切换shell为bashsh-4.2$
sh-4.2$ bash
[localadmin@server111 ~]$
[localadmin@server111 ~]$ sh
sh-4.2$
sh-4.2$
# 通过exit命令可以退出bash环境退回到sh环境。
三、命令组合符
1. 逻辑与(&&)
逻辑与(&&)的基本语法格式为:command1 && command2
其含义为:如果command1命令运行成功,则继续运行command2命令。如果command1命令运行失败,则不运行command2命令。
2. 逻辑或(||)
逻辑或(||)的基本语法格式为:command1 || command2
其含义为:如果command1命令运行失败,则继续运行command2命令。如果command1命令运行成功,则不运行command2命令。
3. 分号(;)
分号(;)的基本语法格式为:command1 ; command2
其含义为:command1命令执行完成后会执行command2命令。不管command1是否执行成功,都会执行command2。
四、syntax
1.解释器
!和/之间有没有空格都可
#!/bin/bash
2.须知
{1} 在sh中用ssh登录别的节点执行命令,别的节点反馈的控制台信息也会在执行sh的节点控制台打印出来
3.引号和括号
https://www.zhihu.com/search?type=content&q=shell%20%E5%BC%95%E5%8F%B7
https://zhuanlan.zhihu.com/p/64953183
[1] 双引号、单引号、反引号
[2] $ + 括号
[3] 括号、双括号
3.入参
标识符 | desc |
---|---|
$n | $1 代表命令本身、$1-$9 代表第1到9个参数,10以上参数用花括号,如 ${10}。 |
$* | 命令行中所有参数,且把所有参数看成一个整体。 |
$@ | 命令行中所有参数,且把每个参数区分对待。 |
$# | 所有参数个数。 |
$$ | 当前进程的 PID 进程号。 |
$! | 后台运行的最后一个进程的 PID 进程号。 |
$? | 最后一次执行的命令的返回状态,0为执行正确,非0执行失败。 |
给shell传参
默认用空格分割,如果一个参数本身带有空格,可以用单、双引号 引起来,引号范围内的所有字符都认为是没有特殊含义的文本。如果不在引号范围内,可以用\
表示转义
"etl_date = '202001' AND khh = '123' "
etl_date = \'202001\'
4.变量
系统变量
变量 | 含义 |
---|---|
$0 | 当前脚本的文件名。 |
$n(n≥1) | 传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是 $1,第二个参数是 $2。 |
$# | 传递给脚本或函数的参数个数。 |
$* | 传递给脚本或函数的所有参数。 |
$@ | 传递给脚本或函数的所有参数。当被双引号" "包含时,$@ 与 $* 稍有不同,我们将在《Shell ∗ 和 *和 ∗和@的区别》一节中详细讲解。 |
$? | 上个命令的退出状态,或函数的返回值,我们将在《Shell $?》一节中详细讲解。 |
$$ | 当前 Shell 进程 ID。对于 Shell 脚本,就是这些脚本所在的进程 ID。 |
{1} 定义变量:变量名=变量值,注意等号两边不要有空格,否则会把空格也作为名字的一部分,一定要紧凑
。
变量名可以由字母、数字、下划线组成,不能以数字开头。
{2} 使用变量:$变量名
{3} usage
[1] 将命令的返回值赋给变量
A=`ls` 反引号,执行里面的命令
A=$(ls) 等价于反引号
[2] 环境变量
/etc/profile只在系统启动时自动执行,所以修改之后需要手动source
5. 运算符
shell默认全部都是文本,想要计算表达式,需要放在特殊的标识中,需要设置运算符。
在 Shell 编程中有各种运算操作,语法格式为 $((运算式)) 或 $[运算式] 或者 expr m + n;如果希望将 expr 的值赋给某个变量,放在 `` 即可。
3种方式:
{1} $((运算式))
echo $(((2+3)*4))
{2} $[运算式],这里的中括号不是条件判断,跟if不同,前后不需要空格
echo $[(2+3)*4]
{3} 使用 expr
expr m + n 注意 expr 运算符间要有空格expr m - n
result3=`expr 2 + 3`
result4=`expr $result3 \* 4`
echo "expr res4=$result4"
*,/,% 分别代表乘,除,取余。乘号*在``中需要转义,但如果在上面2种中,不需要转
TEMP=`expr 2 + 3`
echo `expr $TEMP * 4`
6.流程控制
{1} 选择
https://blog.csdn.net/star_kite/article/details/124934333
[1]条件判断表达式
条件判断使用语法 [ condition ](注意 condition 前后有空格),非空会返回 true。可以使用 $? 验证结果,0 为 true,>1 为false
- [ hspEdu ] 会返回 true
- [ ] 会返回 false
- [ condition ] && echo yes || echo no ,前一个判断满足时会继续执行后面的语句
if [ $a == $b ]
then
echo "a is equal to b"
elif [ $a -gt $b ]
then
echo "a is greater than b"
elif [ $a -lt $b ]
then
echo "a is less than b"
else
echo "None of the condition met"
fi
[2]条件判断分类,字符串、数值、文件
{2} 循环
shell中的循环有2种形式for(())和for i in x x x,x x x可以不带引号,可以直接用变量,比如\$1、\$*
https://www.cnblogs.com/EasonJim/p/8315939.html
for i in {1..5}
do
echo $i
done
for i in 5 6 7 8 9 #是空格不是逗号
do
echo $i
done
#循环数组${arr[@]} 不能用$arr,这样只有第一个元素
#"${arr[@]}" 要用双引号包裹,避免数组中元素出现莫名其妙的切分
#定义数组间隔空格,字符串需要加双引号
#shell中的空格要用空格,不能用tab
arr=("13-2020年年度个人总结" "14-2021年年度个人总结" "15-2020和2021年绩效考核结果及说明" "16-2020年绩效考核办法" "17-2021年绩效考核办法")
for i in "${arr[@]}"; do
echo $i"(无).pdf"
done
for((host=105;host < 108;host++));
do
ssh node$host /opt/cm-5.16.2/etc/init.d/cloudera-scm-agent $1
done
for FILE in $HOME/.bash*
do
echo $FILE
done
break #跳出所有循环
break n #跳出第n层f循环
continue #跳出当前循环
COUNTER=0
while [ $COUNTER -lt 5 ]
do
COUNTER=`expr $COUNTER + 1`
echo $COUNTER
done
{3} switch
case $变量名 in
"值 1")
;;
如果变量的值等于值1,则执行程序1,值
"值 2")
如果变量的值等于值2,则执行程序2
…省略其他分支…
;;
*)
如果变量的值都不是以上的值,则执行此程序
;;
esac
7.函数
shell中定义函数有3种方式,这3种方式等价
f_name(){……}
function f_name{……}
function f_name(){……}
传参,注意定义时的括号里面不需要形参,在函数体中通过$num直接用即可。注意,函数中的\$num和脚本的\$num不同
。
test(){
echo $1 #接收第一个参数
echo $2 #接收第二个参数
echo $3 #接收第三个参数
echo $# #接收到参数的个数
echo $* #接收到的所有参数
}
test aa bb cc
如果这个函数想要在脚本之外用,EXPORT 函数名
调用时,直接函数名 arg1 arg2
8.重定向
linux也有流的概念,有流就有源和目的地,目的地有很多种,可以换。这个换就是重定向。既然是写,也就有append和overwrite的区别。
1是标准输出,其实就是日志
2是错误,报错时的输出,相当于System.err
$echo result > file #将结果写入文件,结果不会在控制台展示,而是在文件中,覆盖写
$echo result >> file #将结果写入文件,结果不会在控制台展示,而是在文件中,追加写
9.EOF
在shell脚本中,通常将EOF与 << 结合使用,表示后续的输入作为子命令或子Shell的输入,直到遇到EOF为止,再返回到主Shell。
EOF只是一个分界符,当然也可以用abcde替换。
当shell遇到<<时,它知道下一个词是一个分界符。在该分界符以后的内容都被当作输入,直到shell又看到该分界符(位于单独的一行)。
此分界符可以是所定义的任何字符串,其实,不一定要用EOF,只要是“内容段”中没有出现的字符串,都可以用来替代EOF,完全可以换成abcde之类的字符串,只是一个起始和结束的标志罢了。
syntax
<<EOF xxx EOF
EOF中的内容不需要加分号,换行分隔即可
notice
- 1.结束的EOF后面不要带分号,并且2个EOF之间的部分前面不要有tab和空格。否则会语法错。下图的是正确的:
非阻塞、后台执行
在命令最后加&
表示后台运行,但即使后台运行,日志也会打印到console,此时需要>/dev/null 2>&1
。但此时的后台运行仅在当前终端有效,如果当前账户退出???
,程序仍会停止,此时就需要nohup。
须知:
>/dev/null 2>&1
> 表示重定向
/dev/null代表空设备文件,又叫黑洞,也就是不输出任何信息到终端
1 表示stdout 标准输出,系统默认值是 1
所以 > /dev/null 相当于 1 > /dev/null
2 表示 stderr 标准错误
即 >/dev/null 2>&1 含义即是将 标准输出、标准错误重定向至空设备文件
nohup 不挂断的运行
no hang up
忽略挂起信号的情况下运行给定的命令,以便注销后命令可以在后台继续运行。
一般情况 nohup ./test &
,此时会默认把日志输出到当前路径下的nohup.out文件中,如果不想要,可以
nohup ./test > myout.txt 2>&1 &
或nohup ./test >/dev/null 2>&1 &
& 后台运行
example
提交spark任务时,需要使用命令调用脚本,而spark-submit默认是在前台阻塞的,脚本执行后都会开1个进程。阻塞的意思就是这个脚本执行后,这个进程会一直在,直到脚本中的程序执行完。
ps aux
此时可以在脚本的命令中设置nohup 和 &来非阻塞执行。
case
1.CDH启停
#! /bin/bash
#1 判断参数个数,如无参数输入,直接退出
argscount=$#
if((argscount == 0));
then
echo ============================no args============================
exit;
fi
#2 函数封装
function start(){
echo ============================启动CDH:begin============================
# 判断mysql是否启动,如果没启动,先启动mysql
mysqlPortStat=`netstat -anp | grep 3306 | wc -l`
if [ $mysqlPortStat == 0 ]
then
ssh node105 systemctl start mysqld
fi
echo "mysql 已经启动"
# 启动CDH
sleep 1s;
ssh node105 /opt/cm-5.16.2/etc/init.d/cloudera-scm-server $1
for((host=105;host < 108;host++));
do
ssh node$host /opt/cm-5.16.2/etc/init.d/cloudera-scm-agent $1
done
# 获取当前前后时间的从1970UTC为止的秒数
start_datetime=`date +%s`
# CDH启动最起码需要等待30s,所以先sleep 30s; 然后再轮循
sleep 30s;
# 监控7180端口,如果监听到数量大于0,说明CDH启动完成
CDHPortCount=`netstat -anp | grep 7180 | wc -l`
while [ $CDHPortCount == 0 ]
do
CDHPortCount=`netstat -anp | grep 7180 | wc -l`
sleep 5s;
done
end_datetime=`date +%s`
echo $end_datetime
echo ============================启动CDH:successed,等待$[end_datetime-start_datetime]s============================
}
function stop(){
echo ============================停止CDH:begin============================
for((host=105;host < 108;host++));do
ssh node$host /opt/cm-5.16.2/etc/init.d/cloudera-scm-agent $1
done
ssh node105 /opt/cm-5.16.2/etc/init.d/cloudera-scm-server $1
echo ============================停止CDH:successed============================
}
function restart(){
# 判断mysql是否启动,如果没启动,先启动mysql
mysqlPortStat=`netstat -anp | grep 3306 | wc -l`
if [ $mysqlPortStat == 0 ]
then
ssh node105 systemctl start mysqld
fi
echo "mysql 已经启动"
#
echo ============================重启CDH:begin============================
for((host=105;host < 108;host++));
do
ssh node$host /opt/cm-5.16.2/etc/init.d/cloudera-scm-agent $1
done
ssh node105 /opt/cm-5.16.2/etc/init.d/cloudera-scm-server $1
echo ============================重启CDH:successed============================
}
#3 根据输入参数执行相应操作
case $1 in
"start")
start $1;
;;
"stop")
stop $1;
;;
"restart")
restart $1;
;;
esac
2.rrsync分发脚本
#!/bin/bash
#1 获取输入参数个数,如果没有参数,直接退出
pcount=$#
if((pcount==0)); then
echo no args;
exit;
fi
#2 获取文件名称
p1=$1
fname=`basename $p1`
echo fname=$fname
#3 获取上级目录到绝对路径
pdir=`cd -P $(dirname $p1); pwd`
echo pdir=$pdir
#4 获取当前用户名称
user=`whoami`
#5 循环
for((host=105; host<108; host++)); do
echo ------------------- node$host --------------
rsync -rvl $pdir/$fname $user@node$host:$pdir
done
3.xcall统一执行脚本
#! /bin/bash
for i in node105 node106 node107
do
echo --------- $i ----------
ssh $i "$*"
done
4.递归目录下所有文件,并替换
在当前目录下,查找最近2天的sh文件,然后把里面的'/*/*'替换为'/*/*/*'
find . -type f -name '*.sh' -ctime -2 | xargs sed -i 's/\/\*\/\*/\/\*\/\*\/\*/g'
5.递归执行目录下的所有sh脚本
递归执行当前目录下所有sh脚本,注意:
最后的\不能省,因为会被认为一行没结束,所以后面加分号;
find . -maxdepth 1 -name '*.sh' -exec {} \;