Shell入门

绝地灬酷狼 2024-04-03 14:15 207阅读 0赞

Shell

文章目录

  • Shell
    • Shell入门
      • Shell字符串+数组
      • Shell运算符
      • Shell流程控制
      • Shell函数
    • Shell并发
      • 方法一:无限并发
      • 方法二:管道限制线程数并发
      • 知识储备: 管道文件
      • 知识储备: wait、sleep、多线程&
    • Shell模板
    • Shell常见问题

Shell入门

什么是Shell?

简单来说“Shell 编程就是对一堆 Linux 命令的逻辑化处理”。

Shell字符串+数组

字符串是 shell 编程中最常用最有用的数据类型(除了数字和字符串,也没啥其它类型好用了),字符串可以用单引号,也可以用双引号。这点和 Java 中有所不同。

字符串拼接

  1. #!/bin/bash
  2. #获取自己定义的变量值
  3. $变量名
  4. ${变量名}
  5. #字符串拼接(拿双引号或单引号,引起来)
  6. name="SnailClimb"
  7. # 使用双引号拼接
  8. greeting="hello, "$name" !"
  9. greeting_1="hello, ${name} !"
  10. echo $greeting $greeting_1
  11. # 使用单引号拼接
  12. greeting_2='hello, '$name' !'
  13. greeting_3='hello, ${name} !'
  14. echo $greeting_2 $greeting_3

获取字符串长度

  1. #!/bin/bash
  2. #获取字符串长度
  3. name="SnailClimb"
  4. # 第一种方式
  5. echo ${
  6. #name} #输出 10
  7. # 第二种方式
  8. expr length "$name";
  9. # 使用 expr 命令时,表达式中的运算符左右必须包含空格,如果不包含空格,将会输出表达式本身:
  10. expr 5+6 // 直接输出 5+6
  11. expr 5 + 6 // 输出 11
  12. # 对于某些运算符,还需要我们使用符号\进行转义,否则就会提示语法错误。
  13. expr 5 * 6 // 输出错误
  14. expr 5 \* 6 // 输出30

截取字符串

  1. #字符串截取: ${str:0:10}
  2. #从字符串第 1 个字符开始往后截取 10 个字符
  3. str="SnailClimb is a great man"
  4. echo ${str:0:10} #输出:SnailClimb
  5. var="https://www.runoob.com/linux/linux-shell-variable.html"
  6. # %表示删除(从后匹配,最短结果)
  7. s1=${var%t*} #https://www.runoob.com/linux/linux-shell-variable.h
  8. # %%表示删除(从后匹配,最长匹配结果)
  9. s2=${var%%t*} #h
  10. s3=${var%%.*} #http://www
  11. # #表示删除(从头匹配, 最短结果)
  12. s4=${var#*/} #/www.runoob.com/linux/linux-shell-variable.html
  13. # ##表示删除从头匹配, 最长匹配结果
  14. s5=${var##*/} #linux-shell-variable.html
  15. # 注: *为通配符, 意为匹配任意数量的任意字符

Shell数组

bash 支持一维数组,并且没有限定数组的大小。

  1. #!/bin/bash
  2. array=(1 2 3 4 5);
  3. #获取数组长度
  4. length=${
  5. #array[@]}
  6. #或者
  7. length2=${
  8. #array[*]}
  9. #输出数组长度
  10. echo $length #输出:5
  11. echo $length2 #输出:5
  12. #输出数组第三个元素
  13. echo ${array[2]} #输出:3
  14. #删除下标为1的元素也就是删除第二个元素
  15. unset array[1]
  16. for i in ${array[@]};do echo $i ;done # 遍历数组,输出: 1 3 4 5
  17. #删除数组中的所有元素
  18. unset array;
  19. for i in ${array[@]};do echo $i ;done # 遍历数组,数组元素为空,没有任何输出内容

Shell运算符

1.算数运算符(与java一样 + - * / % = == !=),此处省略

  1. #!/bin/bash
  2. a=3;b=3;
  3. val=`expr $a + $b` #`` 不是单引号,是反引号
  4. #输出:Total value : 6
  5. 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。

  1. #!/bin/bash
  2. score=90;
  3. maxscore=100;
  4. if [ $score -eq $maxscore ]
  5. then
  6. echo "A"
  7. else
  8. echo "B"
  9. fi
  10. #输出结果:B

3.逻辑运算符




































运算符 说明 举例
&& 逻辑AND
|| 逻辑OR
-o 或运算
-a 与运算

4.字符串运算符




































运算符 说明 举例
= 检测两个字符串是否相等,相等返回true
!= 检测两个字符串是否相等,不相等返回true
-z 检测字符串长度是否为0,为0返回true
-n 检测字符串长度是否为0,不为0返回true
str 检测字符串是否为空,不为空返回true
  1. #!/bin/bash
  2. a="abc";
  3. b="efg";
  4. if [ $a = $b ]
  5. then
  6. echo "a 等于 b"
  7. else
  8. echo "a 不等于 b"
  9. fi
  10. #输出: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 条件语句

  1. #!/bin/bash
  2. a=3;
  3. b=9;
  4. if [ $a -eq $b ]
  5. then
  6. echo "a 等于 b"
  7. elif [ $a -gt $b ]
  8. then
  9. echo "a 大于 b"
  10. else
  11. echo "a 小于 b"
  12. fi
  13. #输出: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”],依然正确。当? 不为空时,两者是一样的。

  1. $ cat a.sh
  2. #!/bin/bash
  3. if [ X$1 = X ]
  4. then
  5. echo "the first argu is empty"
  6. else
  7. echo "the first argu is $1"
  8. fi
  9. $ ./a.sh
  10. the first argu is empty
  11. $ ./a.sh 123
  12. the first argu is 123

2. for 循环语句

  1. # 输出当前列表中的数据
  2. #!/bin/bash
  3. for loop in 1 2 3 4 5
  4. do
  5. echo "The value is: $loop"
  6. done
  7. # 产生10个随机数
  8. #!/bin/bash
  9. for i in {
  10. 0..9};
  11. do
  12. echo $RANDOM;
  13. done
  14. # 输出1~5
  15. #!/bin/bash
  16. for((i=1;i<=5;i++));do
  17. echo $i;
  18. done;

注意:通常情况下 shell 变量调用需要加 $,但是 for 的 (()) 中不需要

3. while 循环语句

基本的 while 循环语句:

  1. #!/bin/bash
  2. int=1
  3. while(( $int<=5 ))
  4. do
  5. echo $int
  6. let "int++"
  7. done

while 循环可用于读取键盘信息

  1. echo '按下 <CTRL-D> 退出'
  2. echo -n '输入你最喜欢的电影: '
  3. while read FILM
  4. do
  5. echo "是的!$FILM 是一个好电影"
  6. done
  7. 按下 <CTRL-D> 退出
  8. 输入你最喜欢的电影: 变形金刚
  9. 是的!变形金刚 是一个好电影

Shell函数

1. 不带参数没有返回值

  1. #!/bin/bash
  2. hello(){
  3. echo "这是我的第一个 shell 函数!"
  4. }
  5. echo "-----函数开始执行-----"
  6. hello
  7. echo "-----函数执行完毕-----"
  8. #输出结果:
  9. -----函数开始执行-----
  10. 这是我的第一个 shell 函数!
  11. -----函数执行完毕-----

2. 有返回值函数

  1. #!/bin/bash
  2. funWithReturn(){
  3. echo "输入第一个数字: "
  4. read aNum
  5. echo "输入第二个数字: "
  6. read anotherNum
  7. echo "两个数字分别为 $aNum 和 $anotherNum !"
  8. return $(($aNum+$anotherNum))
  9. }
  10. funWithReturn
  11. echo "输入的两个数字之和为 $?"
  12. #输出结果:
  13. 输入第一个数字:
  14. 1
  15. 输入第二个数字:
  16. 2
  17. 两个数字分别为 1 2 !
  18. 输入的两个数字之和为 3

3. 有参数的函数

  1. #!/bin/bash
  2. funWithParam(){
  3. echo "第一个参数为 $1 !"
  4. echo "第二个参数为 $2 !"
  5. echo "第十个参数为 $10 !"
  6. echo "第十个参数为 ${10} !"
  7. echo "第十一个参数为 ${11} !"
  8. echo "参数总数有 $# 个!"
  9. echo "作为一个字符串输出所有参数 $* !"
  10. }
  11. funWithParam 1 2 3 4 5 6 7 8 9 34 73
  12. #输出结果:
  13. 第一个参数为 1 !
  14. 第二个参数为 2 !
  15. 第十个参数为 10 !
  16. 第十个参数为 34 !
  17. 第十一个参数为 73 !
  18. 参数总数有 11 个!
  19. 作为一个字符串输出所有参数 1 2 3 4 5 6 7 8 9 34 73 !

Shell并发

方法一:无限并发

一个for循环1000次,循环体里面的每个任务都放入后台运行(在命令后面加&符号代表后台运行)

  1. #!/bin/bash
  2. start=`date +%s` #定义脚本运行的开始时间
  3. for ((i=1;i<=1000;i++))
  4. do
  5. {
  6. sleep 1 #sleep 1用来模仿执行一条命令需要花费的时间(可以用真实命令来代替)
  7. echo 'success'$i;
  8. }& #用{}把循环体括起来,后加一个&符号,代表每次循环都把命令放入后台运行
  9. #一旦放入后台,就意味着{}里面的命令交给操作系统的一个线程处理了
  10. #循环了1000次,就有1000个&把任务放入后台,操作系统会并发1000个线程来处理
  11. #这些任务
  12. done
  13. wait #wait命令的意思是,等待(wait命令)上面的命令(放入后台的)都执行完毕了再
  14. #往下执行。
  15. #在这里写wait是因为,一条命令一旦被放入后台后,这条任务就交给了操作系统
  16. #shell脚本会继续往下运行(也就是说:shell脚本里面一旦碰到&符号就只管把它
  17. #前面的命令放入后台就算完成任务了,具体执行交给操作系统去做,脚本会继续
  18. #往下执行),所以要在这个位置加上wait命令,等待操作系统执行完所有后台命令
  19. end=`date +%s` #定义脚本运行的结束时间
  20. echo "TIME:`expr $end - $start`"

运行结果:

  1. [root@iZ94yyzmpgvZ /]# . test1.sh
  2. ......
  3. [989] Done {
  4. sleep 1; echo 'success'$i; }
  5. [990] Done {
  6. sleep 1; echo 'success'$i; }
  7. success992
  8. [991] Done {
  9. sleep 1; echo 'success'$i; }
  10. [992] Done {
  11. sleep 1; echo 'success'$i; }
  12. success993
  13. [993] Done {
  14. sleep 1; echo 'success'$i; }
  15. success994
  16. success995
  17. [994] Done {
  18. sleep 1; echo 'success'$i; }
  19. success996
  20. [995] Done {
  21. sleep 1; echo 'success'$i; }
  22. [996] Done {
  23. sleep 1; echo 'success'$i; }
  24. success997
  25. success998
  26. [997] Done {
  27. sleep 1; echo 'success'$i; }
  28. success999
  29. [998] Done {
  30. sleep 1; echo 'success'$i; }
  31. [999]- Done {
  32. sleep 1; echo 'success'$i; }
  33. success1000
  34. [1000]+ Done {
  35. sleep 1; echo 'success'$i; }
  36. TIME:2

方法二:管道限制线程数并发

思路:基于上面的无限并发,使用linux管道文件特性制作队列,控制线程数目

创建有名管道文件exec 3<>/tmp/fd1,创建文件描述符3关联管道文件,这时候3这个文件描述符就拥有了管道的所有特性,还具有一个管道不具有的特性:无限存不阻塞,无限取不阻塞,而不用关心管道内是否为空,也不用关心是否有内容写入引用文件描述符: &3可以执行n次echo >&3 往管道里放入n把钥匙

实现:【并发模板,拿来即用】

  1. #!/bin/bash
  2. start_time=`date +%s` #定义脚本运行的开始时间
  3. [ -e /tmp/fd1 ] || mkfifo /tmp/fd1 #创建有名管道
  4. exec 3<>/tmp/fd1 #创建文件描述符,以可读(<)可写(>)的方式关联管道文件,这时候文件描述符3就有了有名管道文件的所有特性
  5. rm -rf /tmp/fd1 #关联后的文件描述符拥有管道文件的所有特性,所以这时候管道文件可以删除,我们留下文件描述符来用就可以了
  6. for ((i=1;i<=10;i++))
  7. do
  8. echo >&3 #&3代表引用文件描述符3,这条命令代表往管道里面放入了一个"令牌"
  9. done
  10. for ((i=1;i<=1000;i++))
  11. do
  12. read -u3 #代表从管道中读取一个令牌
  13. {
  14. sleep 1 #sleep 1用来模仿执行一条命令需要花费的时间(可以用真实命令来代替)
  15. echo 'success'$i
  16. echo >&3 #代表我这一次命令执行到最后,把令牌放回管道
  17. }&
  18. done
  19. wait
  20. stop_time=`date +%s` #定义脚本运行的结束时间
  21. echo "TIME:`expr $stop_time - $start_time`"
  22. exec 3<&- #关闭文件描述符的读
  23. exec 3>&- #关闭文件描述符的写

结果:

  1. [root@iZ94yyzmpgvZ /]# . test2.sh
  2. success4
  3. success6
  4. success7
  5. success8
  6. success9
  7. success5
  8. ......
  9. success935
  10. success941
  11. success942
  12. ......
  13. success992
  14. [992] Done {
  15. sleep 1; echo 'success'$i; echo 1>&3; }
  16. success993
  17. [993] Done {
  18. sleep 1; echo 'success'$i; echo 1>&3; }
  19. success994
  20. [994] Done {
  21. sleep 1; echo 'success'$i; echo 1>&3; }
  22. success998
  23. success999
  24. success1000
  25. success997
  26. success995
  27. success996
  28. [995] Done {
  29. sleep 1; echo 'success'$i; echo 1>&3; }
  30. TIME:101

知识储备: 管道文件

  1. 无名管道(ps aux | grep nginx)
  2. 有名管道(mkfifo /tmp/fd1)

有名管道特性:

  1. cat /tmp/fd1(如果管道内容为空,则阻塞)
  2. echo "test" > /tmp/fd1(如果没有读管道的操作,则阻塞)

实验:

  1. 窗口1
  2. [sichuan@eb6326 lihewei]$ mkfifo /tmp/fd1
  3. [sichuan@eb6326 lihewei]$ ll /tmp/fd1
  4. prw-rw-r-- 1 sichuan sichuan 0 10-12 17:18 /tmp/fd1
  5. [sichuan@eb6326 lihewei]$ cat /tmp/fd1
  6. 阻塞...
  7. [sichuan@eb6326 lihewei]$ echo 'test' > /tmp/fd1
  8. 窗口2
  9. [sichuan@eb6326 ~]$ cat /tmp/fd1 #此时的状态会一直阻塞,知道管道中有数据
  10. test

知识储备: wait、sleep、多线程&

  • wait:使用 wait 是在等待上一批或上一个脚本执行完(即上一个的进程终止),再执行wait之后的命令。
  • sleep:是使系统休眠一定的时间之后再去执行下面的任务。
  • 多线程:用{}把循环体括起来,后加一个 & 符号,代表每次循环都把命令放入后台运行,一旦放入后台,就意味着{}里面的命令交给操作系统的一个线程处理了,循环了1000次,就有1000个&把任务放入后台,操作系统会并发1000个线程来处理.

    wait [进程号或作业号]

    wait 22 等待22进程完在执行下面的
    wait %1 第一个作业
    wait 等待所有后台进程结束

  1. # sleep, $! 表示上一个子进程的进程号
  2. #!/bin/bash
  3. $ cat wait.sh
  4. sleep 10 &
  5. sleep 5 &
  6. wait $!
  7. $ time sh wait.sh
  8. real 0m5.005s /可见等待上一个进程只要5s,如果只有wait则需要等待10s
  9. user 0m0.001s
  10. sys 0m0.003s

Shell模板

脚本启动sh文件模板(拿来即用)

  1. #!/bin/bash
  2. #这里可替换为你自己的执行程序,其他代码无需更改
  3. APP_NAME=getMd5Info.jar
  4. CUR_SHELL_DIR=`pwd`
  5. JAR_PATH=$CUR_SHELL_DIR/$APP_NAME
  6. #LOG_PATH=./logs/start.log
  7. LOG_PATH=$CUR_SHELL_DIR/$APP_NAME.log
  8. #LOG_PATH=$LOG_DIR/${JAR_NAME}.log
  9. SPRING_PROFILES_ACTIV=""
  10. #JAVA_MEM_OPTS=" -Xms2048M -Xmx2048M -XX:MetaspaceSize=512M -XX:MaxMetaspaceSize=512M -XX:+UseParallelGC"
  11. #启动时指定配置文件
  12. 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"
  13. #JAVA_MEM_OPTS=""
  14. #使用说明,用来提示输入参数
  15. if [[ "$2" = "" ]] || [[ "$1" = "" ]];
  16. then
  17. echo -e "\033[0;31m 参数数据不完整 \033[0m \033[0;34m {如果是xxx.jar,使用: ./boot.sh start xxx.jar || ./boot.sh stop xxx.jar } \033[0m"
  18. exit 1
  19. fi
  20. usage() {
  21. echo "Usage: sh 脚本名.sh [start|stop|restart|status]"
  22. exit 1
  23. }
  24. #检查程序是否在运行
  25. is_exist(){
  26. pid=`ps -ef|grep $APP_NAME|grep java|grep -v grep|grep -v kill|awk '{print $2}'`
  27. #如果不存在返回1,存在返回0
  28. if [ -z "${pid}" ]; then
  29. return 1
  30. else
  31. return 0
  32. fi
  33. }
  34. #启动方法
  35. start(){
  36. is_exist
  37. if [ $? -eq "0" ]; then
  38. echo "${APP_NAME} is already running. pid=${pid} ."
  39. else
  40. nohup java $JAVA_MEM_OPTS -jar $JAR_PATH >> $LOG_PATH 2>&1 &
  41. echo "${APP_NAME} start success"
  42. fi
  43. }
  44. #停止方法
  45. stop(){
  46. is_exist
  47. if [ $? -eq "0" ]; then
  48. kill -9 $pid
  49. echo "${APP_NAME} is stop success"
  50. else
  51. echo "${APP_NAME} is not running"
  52. fi
  53. }
  54. #输出运行状态
  55. status(){
  56. is_exist
  57. if [ $? -eq "0" ]; then
  58. echo "${APP_NAME} is running. Pid is ${pid}"
  59. else
  60. echo "${APP_NAME} is NOT running."
  61. fi
  62. }
  63. #重启
  64. restart(){
  65. stop
  66. start
  67. }
  68. #根据输入参数,选择执行对应方法,不输入则执行使用说明
  69. case "$1" in
  70. "start")
  71. start
  72. ;;
  73. "stop")
  74. stop
  75. ;;
  76. "status")
  77. status
  78. ;;
  79. "restart")
  80. restart
  81. ;;
  82. *)
  83. usage
  84. ;;
  85. esac

Shell常见问题

-bash: /bin/bash^M: bad interpreter: 没有那个文件或目录

Linux和Windows之间的不完全兼容出现的问题。因为操作系统是Windows,我在Windows下编辑的脚本,所以有可能会存在不可见的字符,监本文件是dos格式的,即每一行的行尾以Windows默认的格式来进行表示。

解决方法:

  1. sed -i 's/\r$//' filename #flename即shell脚本文件名
  2. 这个命令会把以\r结束的字符换成空白
  3. #例如
  4. sed -i 's/\r$//' request1.sh

发表评论

表情:
评论列表 (有 0 条评论,207人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Shell一、Shell入门

    一、什么是shell 在使用操作系统时,我们可以使用图形界面或者命令行启动某个程序,这两者的基本过程都是类似的,都需要查找程序在硬盘上的安装位置,然后将它们加载到内存运行

    相关 shell入门教程

    从程序员的角度来看, Shell本身是一种用C语言编写的程序,从用户的角度来看,Shell是用户与Linux操作系统沟通的桥梁。用户既可以输入命令执行,又可以利用 Shell脚

    相关 Shell 编程入门

    一、Shell简介          在计算机科学中,Shell俗称壳(用来区别于核),是指“为使用者提供操作界面”的软件(命令解析器)。它类似于[DOS][]下的com