Shell入门
Shell
文章目录
- Shell
- Shell入门
- Shell字符串+数组
- Shell运算符
- Shell流程控制
- Shell函数
- Shell并发
- 方法一:无限并发
- 方法二:管道限制线程数并发
- 知识储备: 管道文件
- 知识储备: wait、sleep、多线程&
- Shell模板
- Shell常见问题
Shell入门
什么是Shell?
简单来说“Shell 编程就是对一堆 Linux 命令的逻辑化处理”。
Shell字符串+数组
字符串是 shell 编程中最常用最有用的数据类型(除了数字和字符串,也没啥其它类型好用了),字符串可以用单引号,也可以用双引号。这点和 Java 中有所不同。
字符串拼接
#!/bin/bash
#获取自己定义的变量值
$变量名
${变量名}
#字符串拼接(拿双引号或单引号,引起来)
name="SnailClimb"
# 使用双引号拼接
greeting="hello, "$name" !"
greeting_1="hello, ${name} !"
echo $greeting $greeting_1
# 使用单引号拼接
greeting_2='hello, '$name' !'
greeting_3='hello, ${name} !'
echo $greeting_2 $greeting_3
获取字符串长度
#!/bin/bash
#获取字符串长度
name="SnailClimb"
# 第一种方式
echo ${
#name} #输出 10
# 第二种方式
expr length "$name";
# 使用 expr 命令时,表达式中的运算符左右必须包含空格,如果不包含空格,将会输出表达式本身:
expr 5+6 // 直接输出 5+6
expr 5 + 6 // 输出 11
# 对于某些运算符,还需要我们使用符号\进行转义,否则就会提示语法错误。
expr 5 * 6 // 输出错误
expr 5 \* 6 // 输出30
截取字符串
#字符串截取: ${str:0:10}
#从字符串第 1 个字符开始往后截取 10 个字符
str="SnailClimb is a great man"
echo ${str:0:10} #输出:SnailClimb
var="https://www.runoob.com/linux/linux-shell-variable.html"
# %表示删除(从后匹配,最短结果)
s1=${var%t*} #https://www.runoob.com/linux/linux-shell-variable.h
# %%表示删除(从后匹配,最长匹配结果)
s2=${var%%t*} #h
s3=${var%%.*} #http://www
# #表示删除(从头匹配, 最短结果)
s4=${var#*/} #/www.runoob.com/linux/linux-shell-variable.html
# ##表示删除从头匹配, 最长匹配结果
s5=${var##*/} #linux-shell-variable.html
# 注: *为通配符, 意为匹配任意数量的任意字符
Shell数组
bash 支持一维数组,并且没有限定数组的大小。
#!/bin/bash
array=(1 2 3 4 5);
#获取数组长度
length=${
#array[@]}
#或者
length2=${
#array[*]}
#输出数组长度
echo $length #输出:5
echo $length2 #输出:5
#输出数组第三个元素
echo ${array[2]} #输出:3
#删除下标为1的元素也就是删除第二个元素
unset array[1]
for i in ${array[@]};do echo $i ;done # 遍历数组,输出: 1 3 4 5
#删除数组中的所有元素
unset array;
for i in ${array[@]};do echo $i ;done # 遍历数组,数组元素为空,没有任何输出内容
Shell运算符
1.算数运算符(与java一样 + - * / % = == !=),此处省略
#!/bin/bash
a=3;b=3;
val=`expr $a + $b` #`` 不是单引号,是反引号
#输出:Total value : 6
echo "Total value : $val"
2.关系运算符
运算符 | 说明 | 举例 |
---|---|---|
-eq | 检测两个数是否相等,相等返回true | $a -eq $b 返回true |
-ne | 检测两个数是否不相等,不相等返回true | $a -ne $b 返回true |
-gt | 检测左边的数是否大于右边的,如果是,则返回true | $a -gt $b 返回true |
-lt | 检测左边的数是否小于右边的,如果是,则返回true | $a -lt $b 返回true |
-ge | 检测左边的数是否大于等于右边的,如果是,则返回true | $a -ge $b 返回true |
-le | 检测左边的数是否小于等于右边的,如果是,则返回true | $a -le $b 返回true |
下面 shell 程序的作用是当 score=100 的时候输出 A 否则输出 B。
#!/bin/bash
score=90;
maxscore=100;
if [ $score -eq $maxscore ]
then
echo "A"
else
echo "B"
fi
#输出结果:B
3.逻辑运算符
运算符 | 说明 | 举例 |
---|---|---|
&& | 逻辑AND | |
|| | 逻辑OR | |
! | 非 | |
-o | 或运算 | |
-a | 与运算 |
4.字符串运算符
运算符 | 说明 | 举例 |
---|---|---|
= | 检测两个字符串是否相等,相等返回true | |
!= | 检测两个字符串是否相等,不相等返回true | |
-z | 检测字符串长度是否为0,为0返回true | |
-n | 检测字符串长度是否为0,不为0返回true | |
str | 检测字符串是否为空,不为空返回true |
#!/bin/bash
a="abc";
b="efg";
if [ $a = $b ]
then
echo "a 等于 b"
else
echo "a 不等于 b"
fi
#输出:a 不等于 b
5.文件相关运算符
操作符 | 说明 | 举例 |
---|---|---|
-d file | 检测文件是否是目录,如果是返回true | -d $file |
-r file | 检测文件是否可读,如果是返回true | -r $file |
-w file | 检测文件是否可写,如果是返回true | -w $file |
-x file | 检测文件是否可执行,如果是返回true | -x $file |
-s file | 检测文件是否为空(文件大小是否大于0),不为空返回true | -s $file |
-e file | 检测文件(包括目录)是否存在,如果是,则返回true | -e $file |
> | 将文本输出到xx文件中,每次写入都清空原文件 | echo “hello word” > tem.log |
>> | 将文本输出到xx文件中,每次写入都追加到原文件中 | echo “hello word” >> tem.log |
使用方式很简单,比如我们定义好了一个文件路径file="/usr/learnshell/test.sh"
如果我们想判断这个文件是否可读,可以这样if [ -r $file ]
如果想判断这个文件是否可写,可以这样-w $file
,是不是很简单。
Shell流程控制
1. if 条件语句
#!/bin/bash
a=3;
b=9;
if [ $a -eq $b ]
then
echo "a 等于 b"
elif [ $a -gt $b ]
then
echo "a 大于 b"
else
echo "a 小于 b"
fi
#输出:a 小于 b
常见的案例:
- **if [ “x v a r “ = “ x “ ] : ∗ ∗ 判断 {var}“ = “x” ]:**判断 var”=”x”]:∗∗判断{var}是否为空
- **if [ X” ? “ = = X “ 0 “ ] : ∗ ∗ 判断 ?” == X”0” ]:**判断 ?”==X”0”]:∗∗判断?是否为0
注意:以上写法是为防止出现语法错误。如果不写X,当 ? 为空或未设置时,语句被解释为 i f [ = “ 0 “ ] ,出现语法错误。加上 X 后解释未 i f [ X = X “ 0 “ ] ,依然正确。当 ?为空或未设置时,语句被解释为 if [ = “0” ], 出现语法错误。加上X后解释未 if [ X = X”0” ] ,依然正确。当 ?为空或未设置时,语句被解释为if[=”0”],出现语法错误。加上X后解释未if[X=X”0”],依然正确。当? 不为空时,两者是一样的。
$ cat a.sh
#!/bin/bash
if [ X$1 = X ]
then
echo "the first argu is empty"
else
echo "the first argu is $1"
fi
$ ./a.sh
the first argu is empty
$ ./a.sh 123
the first argu is 123
2. for 循环语句
# 输出当前列表中的数据
#!/bin/bash
for loop in 1 2 3 4 5
do
echo "The value is: $loop"
done
# 产生10个随机数
#!/bin/bash
for i in {
0..9};
do
echo $RANDOM;
done
# 输出1~5
#!/bin/bash
for((i=1;i<=5;i++));do
echo $i;
done;
注意:通常情况下 shell 变量调用需要加 $,但是 for 的 (()) 中不需要
3. while 循环语句
基本的 while 循环语句:
#!/bin/bash
int=1
while(( $int<=5 ))
do
echo $int
let "int++"
done
while 循环可用于读取键盘信息
echo '按下 <CTRL-D> 退出'
echo -n '输入你最喜欢的电影: '
while read FILM
do
echo "是的!$FILM 是一个好电影"
done
按下 <CTRL-D> 退出
输入你最喜欢的电影: 变形金刚
是的!变形金刚 是一个好电影
Shell函数
1. 不带参数没有返回值
#!/bin/bash
hello(){
echo "这是我的第一个 shell 函数!"
}
echo "-----函数开始执行-----"
hello
echo "-----函数执行完毕-----"
#输出结果:
-----函数开始执行-----
这是我的第一个 shell 函数!
-----函数执行完毕-----
2. 有返回值函数
#!/bin/bash
funWithReturn(){
echo "输入第一个数字: "
read aNum
echo "输入第二个数字: "
read anotherNum
echo "两个数字分别为 $aNum 和 $anotherNum !"
return $(($aNum+$anotherNum))
}
funWithReturn
echo "输入的两个数字之和为 $?"
#输出结果:
输入第一个数字:
1
输入第二个数字:
2
两个数字分别为 1 和 2 !
输入的两个数字之和为 3
3. 有参数的函数
#!/bin/bash
funWithParam(){
echo "第一个参数为 $1 !"
echo "第二个参数为 $2 !"
echo "第十个参数为 $10 !"
echo "第十个参数为 ${10} !"
echo "第十一个参数为 ${11} !"
echo "参数总数有 $# 个!"
echo "作为一个字符串输出所有参数 $* !"
}
funWithParam 1 2 3 4 5 6 7 8 9 34 73
#输出结果:
第一个参数为 1 !
第二个参数为 2 !
第十个参数为 10 !
第十个参数为 34 !
第十一个参数为 73 !
参数总数有 11 个!
作为一个字符串输出所有参数 1 2 3 4 5 6 7 8 9 34 73 !
Shell并发
方法一:无限并发
一个for循环1000次,循环体里面的每个任务都放入后台运行(在命令后面加&符号代表后台运行)
#!/bin/bash
start=`date +%s` #定义脚本运行的开始时间
for ((i=1;i<=1000;i++))
do
{
sleep 1 #sleep 1用来模仿执行一条命令需要花费的时间(可以用真实命令来代替)
echo 'success'$i;
}& #用{}把循环体括起来,后加一个&符号,代表每次循环都把命令放入后台运行
#一旦放入后台,就意味着{}里面的命令交给操作系统的一个线程处理了
#循环了1000次,就有1000个&把任务放入后台,操作系统会并发1000个线程来处理
#这些任务
done
wait #wait命令的意思是,等待(wait命令)上面的命令(放入后台的)都执行完毕了再
#往下执行。
#在这里写wait是因为,一条命令一旦被放入后台后,这条任务就交给了操作系统
#shell脚本会继续往下运行(也就是说:shell脚本里面一旦碰到&符号就只管把它
#前面的命令放入后台就算完成任务了,具体执行交给操作系统去做,脚本会继续
#往下执行),所以要在这个位置加上wait命令,等待操作系统执行完所有后台命令
end=`date +%s` #定义脚本运行的结束时间
echo "TIME:`expr $end - $start`"
运行结果:
[root@iZ94yyzmpgvZ /]# . test1.sh
......
[989] Done {
sleep 1; echo 'success'$i; }
[990] Done {
sleep 1; echo 'success'$i; }
success992
[991] Done {
sleep 1; echo 'success'$i; }
[992] Done {
sleep 1; echo 'success'$i; }
success993
[993] Done {
sleep 1; echo 'success'$i; }
success994
success995
[994] Done {
sleep 1; echo 'success'$i; }
success996
[995] Done {
sleep 1; echo 'success'$i; }
[996] Done {
sleep 1; echo 'success'$i; }
success997
success998
[997] Done {
sleep 1; echo 'success'$i; }
success999
[998] Done {
sleep 1; echo 'success'$i; }
[999]- Done {
sleep 1; echo 'success'$i; }
success1000
[1000]+ Done {
sleep 1; echo 'success'$i; }
TIME:2
方法二:管道限制线程数并发
思路:基于上面的无限并发,使用linux管道文件特性制作队列,控制线程数目
创建有名管道文件exec 3<>/tmp/fd1,创建文件描述符3关联管道文件,这时候3这个文件描述符就拥有了管道的所有特性,还具有一个管道不具有的特性:无限存不阻塞,无限取不阻塞,而不用关心管道内是否为空,也不用关心是否有内容写入引用文件描述符: &3可以执行n次echo >&3 往管道里放入n把钥匙
实现:【并发模板,拿来即用】
#!/bin/bash
start_time=`date +%s` #定义脚本运行的开始时间
[ -e /tmp/fd1 ] || mkfifo /tmp/fd1 #创建有名管道
exec 3<>/tmp/fd1 #创建文件描述符,以可读(<)可写(>)的方式关联管道文件,这时候文件描述符3就有了有名管道文件的所有特性
rm -rf /tmp/fd1 #关联后的文件描述符拥有管道文件的所有特性,所以这时候管道文件可以删除,我们留下文件描述符来用就可以了
for ((i=1;i<=10;i++))
do
echo >&3 #&3代表引用文件描述符3,这条命令代表往管道里面放入了一个"令牌"
done
for ((i=1;i<=1000;i++))
do
read -u3 #代表从管道中读取一个令牌
{
sleep 1 #sleep 1用来模仿执行一条命令需要花费的时间(可以用真实命令来代替)
echo 'success'$i
echo >&3 #代表我这一次命令执行到最后,把令牌放回管道
}&
done
wait
stop_time=`date +%s` #定义脚本运行的结束时间
echo "TIME:`expr $stop_time - $start_time`"
exec 3<&- #关闭文件描述符的读
exec 3>&- #关闭文件描述符的写
结果:
[root@iZ94yyzmpgvZ /]# . test2.sh
success4
success6
success7
success8
success9
success5
......
success935
success941
success942
......
success992
[992] Done {
sleep 1; echo 'success'$i; echo 1>&3; }
success993
[993] Done {
sleep 1; echo 'success'$i; echo 1>&3; }
success994
[994] Done {
sleep 1; echo 'success'$i; echo 1>&3; }
success998
success999
success1000
success997
success995
success996
[995] Done {
sleep 1; echo 'success'$i; echo 1>&3; }
TIME:101
知识储备: 管道文件
- 无名管道(ps aux | grep nginx)
- 有名管道(mkfifo /tmp/fd1)
有名管道特性:
cat /tmp/fd1(如果管道内容为空,则阻塞)
echo "test" > /tmp/fd1(如果没有读管道的操作,则阻塞)
实验:
窗口1:
[sichuan@eb6326 lihewei]$ mkfifo /tmp/fd1
[sichuan@eb6326 lihewei]$ ll /tmp/fd1
prw-rw-r-- 1 sichuan sichuan 0 10-12 17:18 /tmp/fd1
[sichuan@eb6326 lihewei]$ cat /tmp/fd1
阻塞...
[sichuan@eb6326 lihewei]$ echo 'test' > /tmp/fd1
窗口2:
[sichuan@eb6326 ~]$ cat /tmp/fd1 #此时的状态会一直阻塞,知道管道中有数据
test
知识储备: wait、sleep、多线程&
- wait:使用 wait 是在等待上一批或上一个脚本执行完(即上一个的进程终止),再执行wait之后的命令。
- sleep:是使系统休眠一定的时间之后再去执行下面的任务。
多线程:用{}把循环体括起来,后加一个 & 符号,代表每次循环都把命令放入后台运行,一旦放入后台,就意味着{}里面的命令交给操作系统的一个线程处理了,循环了1000次,就有1000个&把任务放入后台,操作系统会并发1000个线程来处理.
wait [进程号或作业号]
wait 22 等待22进程完在执行下面的
wait %1 第一个作业
wait 等待所有后台进程结束
# sleep, $! 表示上一个子进程的进程号
#!/bin/bash
$ cat wait.sh
sleep 10 &
sleep 5 &
wait $!
$ time sh wait.sh
real 0m5.005s /可见等待上一个进程只要5s,如果只有wait则需要等待10s
user 0m0.001s
sys 0m0.003s
Shell模板
脚本启动sh文件模板(拿来即用)
#!/bin/bash
#这里可替换为你自己的执行程序,其他代码无需更改
APP_NAME=getMd5Info.jar
CUR_SHELL_DIR=`pwd`
JAR_PATH=$CUR_SHELL_DIR/$APP_NAME
#LOG_PATH=./logs/start.log
LOG_PATH=$CUR_SHELL_DIR/$APP_NAME.log
#LOG_PATH=$LOG_DIR/${JAR_NAME}.log
SPRING_PROFILES_ACTIV=""
#JAVA_MEM_OPTS=" -Xms2048M -Xmx2048M -XX:MetaspaceSize=512M -XX:MaxMetaspaceSize=512M -XX:+UseParallelGC"
#启动时指定配置文件
JAVA_MEM_OPTS=" -server -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=512m -Xms2048m -Xmx2048m -Xmn512m -Xss512k -XX:SurvivorRatio=16 -XX:+UseConcMarkSweepGC -Dspring.config.location=config/application-dev.yml"
#JAVA_MEM_OPTS=""
#使用说明,用来提示输入参数
if [[ "$2" = "" ]] || [[ "$1" = "" ]];
then
echo -e "\033[0;31m 参数数据不完整 \033[0m \033[0;34m {如果是xxx.jar,使用: ./boot.sh start xxx.jar || ./boot.sh stop xxx.jar } \033[0m"
exit 1
fi
usage() {
echo "Usage: sh 脚本名.sh [start|stop|restart|status]"
exit 1
}
#检查程序是否在运行
is_exist(){
pid=`ps -ef|grep $APP_NAME|grep java|grep -v grep|grep -v kill|awk '{print $2}'`
#如果不存在返回1,存在返回0
if [ -z "${pid}" ]; then
return 1
else
return 0
fi
}
#启动方法
start(){
is_exist
if [ $? -eq "0" ]; then
echo "${APP_NAME} is already running. pid=${pid} ."
else
nohup java $JAVA_MEM_OPTS -jar $JAR_PATH >> $LOG_PATH 2>&1 &
echo "${APP_NAME} start success"
fi
}
#停止方法
stop(){
is_exist
if [ $? -eq "0" ]; then
kill -9 $pid
echo "${APP_NAME} is stop success"
else
echo "${APP_NAME} is not running"
fi
}
#输出运行状态
status(){
is_exist
if [ $? -eq "0" ]; then
echo "${APP_NAME} is running. Pid is ${pid}"
else
echo "${APP_NAME} is NOT running."
fi
}
#重启
restart(){
stop
start
}
#根据输入参数,选择执行对应方法,不输入则执行使用说明
case "$1" in
"start")
start
;;
"stop")
stop
;;
"status")
status
;;
"restart")
restart
;;
*)
usage
;;
esac
Shell常见问题
-bash: /bin/bash^M: bad interpreter: 没有那个文件或目录
Linux和Windows之间的不完全兼容出现的问题。因为操作系统是Windows,我在Windows下编辑的脚本,所以有可能会存在不可见的字符,监本文件是dos格式的,即每一行的行尾以Windows默认的格式来进行表示。
解决方法:
sed -i 's/\r$//' filename #flename即shell脚本文件名
这个命令会把以\r结束的字符换成空白
#例如
sed -i 's/\r$//' request1.sh
还没有评论,来说两句吧...