文章目录

  • 前置知识
    • shell变量
      • 环境变量
      • 本地变量
    • shell脚本执行方式
  • 基本语法
    • 通配符
    • 命令代换
    • 算数代换
    • 转义字符
    • 字符串符号
    • 逻辑运算
  • 脚本语法
    • 条件测试
    • 流程控制
    • 位置参数
    • 函数
    • 脚本调试
  • 实例
    • 开机自动脚本
    • 登陆自动脚本
  • 字符串处理
    • 基本操作
    • 字符串截取
      • 字符截取
      • 子串截取
    • 字符串替换
    • 大小写转换
  • 数组
    • 普通数组
    • 关联数组
  • 注意事项

前置知识

shell变量

  按照惯例,Shell变量由全大写字母加下划线组成,有两种类型的Shell变量:

环境变量

  环境变量可以从父进程传给子进程,因此Shell进程的环境变量可以从当前Shell进程传给fork出来的子进程。

$ env  # 打印系统环境变量
$ printenv  # 打印系统环境变量
$ echo $PATH  # PATH环境变量

  实际应用中比较常用的是设置(PATH)环境变量:

  • 对所有用户永久生效

    $ vim /etc/profile
    $ export CLASSPATH=./JAVA_HOME/lib;
    $ export PATH=/usr/local/bin:$PATH
    
  • 仅对当前终端有效

    $ export PATH=/usr/local/bin:$PATH
    
  • 对某一用户永久有效

    $ vim ~/.bashrc
    $ export PATH=/usr/local/bin:$PATH
    
本地变量

  只存在于当前Shell进程,用set命令可以显示当前Shell进程中定义的所有变量(包括本地变量和环境变量)和函数。环境变量是任何进程都有的概念,而本地变量是Shell特有的概念。

$ VAR=value  # 等号左右两边不要有空格,否则会被Shell解释成命令和命令行参数
$ echo $VAR
$ echo ${VAR}
$ $VAR # 特别注意这种方式会当作命令行来执行
>value: command not found$ unset VAR # 删除已定义的环境变量或本地变量

  一个变量定义后仅存在于当前Shell进程,它是本地变量,用export命令可以把本地变量导出为环境变量,定义和导出环境变量通常可以一步完成。

$ export VARNAME=value

shell脚本执行方式

# assume a script like below
# test.sh
cd docker_test
ls
  • ./test.sh

    chmod a+x test.sh
    ./test.sh
    

  Shell会fork一个子进程并调用exec执行./test.sh这个程序,exec系统调用应该把子进程的代码段替换成./test.sh程序的代码段,并从它的_start开始执行。然而test.sh是个文本文件,根本没有代码段和_start函数,怎么办呢?其实exec还有另外一种机制,如果要执行的是一个文本文件,并且第一行用Shebang指定了解释器,则用解释器程序的代码段替换当前进程,并且从解释器的_start开始执行,而这个文本文件被当作命令行参数传给解释器。

  • /bin/bash ./test.sh

    这种执行方式和上一种效果相同,都不会影响交互环境,且不需要test.sh文件具有可执行权限。

  • source ./test.sh

    这种执行方式直接在交互式Shell下执行的,会改变交互式Shell的环境。

  • . ./test.sh

  这种执行方式直接在交互式Shell下执行的,会改变交互式Shell的环境。

小结: (cd ./docker_test;ls -l)相当于前两种方式,去掉括号相当于后两种方式

基本语法

通配符

  通配符(Wildcard)常用于脚本或平常的命令行之中,需要注意的就是所匹配的文件名是由Shell展开的,也就是说在参数还没传给程序之前已经展开了。

# *   匹配0个或多个任意字符
# ?   匹配一个任意字符
# [若干字符]  匹配方括号中任意一个字符的一次出现$ ls /dev/ttyS*
$ ls ch0?.doc
$ ls ch0[0-2].doc
$ ls ch[012]   [0-9].doc

命令代换

  由' '反引号括起来的是一条命令,Shell先执行该命令,然后将输出结果立刻代换到当前命令行中。

$ DATE=`date`
$ echo $DATE# 命令代换也可以使用$()
$ DATE=$(date)

算数代换

  算术代换是指,$(())中的Shell变量取值将转换成整数,与$[]等价:

$ VAR=45
$ echo $(($VAR+3))
$(())中只能用+-*/和()运算符,并且只能做整数运算。$[base#n],其中base表示进制,n按照base进制解释,后面再有运算数,按十进制解释。echo $[2#10+11]
echo $[8#10+11]
echo $[10#10+11]

转义字符

  和C语言类似,\在Shell中被用作转义字符,用于去除紧跟其后的单个字符的特殊意义(回车除外),换句话说,紧跟其后的字符取字面值。例如:

$ echo \\# 在\之后敲回车表示续行
$ cat \
>a.txt

字符串符号

  在shell中,字符串既可以用单引号‘’也可以使用双引号“”表示,但它们之间存在一定的差别。

  • 单引号

      单引号用于保持引号内所有字符的字面值,即使引号内的\和回车也不例外,但是字符串中不能出现单引号。如果引号没有配对就输入回车,Shell会给出续行提示符,要求用户把引号配上对。

    # 对比
    $ echo '$PATH'
    $ echo $PATH
    
  • 双引号

      被双引号用括住的内容,将被视为单一字串。它防止通配符扩展,但允许变量扩展。作为一种好的Shell编程习惯,应该总是把变量取值放在双引号之中。

    # 对比
    $ echo '$PATH'
    $ echo $PATH
    $ echo "$PATH"
    

逻辑运算

  Shell提供了!,&&,||语法,和C语言类似,具有Short-circuit特性。注意和!-a -o(在下面条件测试的内容中会提到)进行区分。

# 如果不是root用户则输出提示语
$ test "$(whoami)" != 'root' && (echo you are using a non-privileged account; exit 1)# 区分-a -o
# 以下写法等价,注意体会
test "$VAR" -gt 1 -a "$VAR" -lt 3  # 连接两个测试条件
test "$VAR" -gt 1 && test "$VAR" -lt 3  # 连接两条语句

脚本语法

条件测试

  条件测试即测试一个条件是否成立,如果测试结果为真,则该命令的Exit Status为0,如果测试结果为假,则命令的Exit Status为1

$ var=2
$ test $var -gt 1
$ echo $?
0$ [ $var -gt 3 ]
$ echo $?
1

  这里需要强调的是,左方括号[是一个命令的名字,传给命令的各参数之间应该用空格隔开,比如,$VAR-gt3][命令的四个参数,它们之间必须用空格隔开。命令test[的参数形式是相同的,只不过test命令不需要]参数。常见测试命令如下:

[ -d DIR ]              如果DIR存在并且是一个目录则为真
[ -f FILE ]             如果FILE存在且是一个普通文件则为真
[ -z STRING ]           如果STRING的长度为零则为真
[ -n STRING ]           如果STRING的长度非零则为真
[ STRING1 = STRING2 ]   如果两个字符串相同则为真
[ STRING1 != STRING2 ]  如果字符串不相同则为真
[ ARG1 OP ARG2 ]        ARG1和ARG2应该是整数或者取值为整数的变量,OP是-eq(等于)-ne(不等于)-lt(小于)-le(小于等于)-gt(大于)-ge(大于等于)之中的一个# 带与、或、非的测试命令
[ ! EXPR ]          EXPR可以是上表中的任意一种测试条件,!表示逻辑反
[ EXPR1 -a EXPR2 ]  EXPR1和EXPR2可以是上表中的任意一种测试条件,-a表示逻辑与
[ EXPR1 -o EXPR2 ]  EXPR1和EXPR2可以是上表中的任意一种测试条件,-o表示逻辑或

流程控制

  需要记住的一点是,如果两条命令写在同一行则需要用;号隔开,一行只写一条命令就不需要写;号。

  • if/then/elif/else/fi

      if命令的参数组成一条子命令,如果该子命令的Exit Status为0(表示真),则执行then后面的子命令,如果Exit Status非0(表示假),则执行elif、else或者fi后面的子命令。if后面的子命令通常是测试命令,但也可以是其它命令。

    #! /bin/bash
    echo "Is it morning? Please answer yes or no."
    read YES_OR_NO
    if [ "$YES_OR_NO" = "yes" ]; then
    echo "Good morning!"
    elif [ "$YES_OR_NO" = "no" ]; then
    echo "Good afternoon!"
    else
    echo "Sorry, $YES_OR_NO not recognized. Enter yes or no."
    exit 1
    fi
    exit 0# 特殊情况
    if :; then echo "always true"; fi  # :表示空操作,Exit Status always True
    
  • case/esac

      case命令可类比C语言的switch/case语句,esac表示case语句块的结束。C语言的case只能匹配整型或字符型常量表达式,而Shell脚本的case可以匹配字符串和Wildcard,每个匹配分支可以有若干条命令,末尾必须以;;结束,执行时找到第一个匹配的分支并执行相应的命令,然后直接跳到esac之后,不需要像C语言一样用break跳出。

    #! /bin/bash
    echo "Is it morning? Please answer yes or no."
    read YES_OR_NO
    case "$YES_OR_NO" in
    yes|y|Yes|YES)
    echo "Good Morning!";;
    [nN]*)
    echo "Good Afternoon!";;
    *)
    echo "Sorry, $YES_OR_NO not recognized. Enter yes or no."
    exit 1;;
    esac
    exit 0
    
  • for/do/done

    #! /bin/bash
    for FILENAME in `ls chap?`
    do
    mv $FILENAME $FILENAME~;
    done
    
  • while/do/done

    #! /bin/bash
    COUNTER=1
    while [ "$COUNTER" -lt 10 ]; do
    echo "Here we go again"
    COUNTER=$(($COUNTER+1))
    done
    
  • break/continue

  break跳出循环,continue跳过本次循环步,与C语言不同的是break[n]可以指定跳出几层循环。

#! /bin/bash
COUNTER=1
while :  # 记住:是空命令
doif test $COUNTER -gt 5;thenbreak;fiCOUNTER=$(($COUNTER+1))
done
echo $COUNTER;

位置参数

$0  相当于C语言main函数的argv[0]
$1、$2...    这些称为位置参数(Positional Parameter),相当于C语言main函数的argv[1]、argv[2]...
$#  相当于C语言main函数的argc - 1,注意这里的#后面不表示注释
$@  表示参数列表"$1" "$2" ...,例如可以用在for循环中的in后面。
$*  表示参数列表"$1" "$2" ...,同上
$?  上一条命令的Exit Status
$$  当前进程号

  位置参数可以用shift命令左移。比如shift 3表示原来的$4现在变成$1,原来的$5现在变成$2等等,原来的$1、$2、$3丢弃,$0不移动。不带参数的shift命令相当于shift 1。

#! /bin/bashecho "The program $0 is now running"echo "The first parameter is $1"echo "The second parameter is $2"echo "The parameter list is $@"shiftecho "The first parameter is $1"echo "The second parameter is $2"echo "The parameter list is $@"

函数

  和C语言类似,Shell中也有函数的概念,但是函数定义中没有返回值也没有参数列表。注意函数体的左花括号’{‘和后面的命令之间必须有空格或换行,如果将最后一条命令和右花括号’}'写在同一行,命令末尾必须有;号。

# 举个例子
is_directory()
{DIR_NAME=$1
if [ ! -d $DIR_NAME ]; thenreturn 1
elsereturn 0
fi
}for DIR in "$@"; do
if is_directory "$DIR"then :elseecho "$DIR doesn't exist. Creating it now..."mkdir $DIR > /dev/null 2>&1if [ $? -ne 0 ]; thenecho "Cannot create directory $DIR"exit 1fi
fi
done

脚本调试

  执行脚本时可以使用相关参数进行相应的调试工作:

-n :读一遍脚本中的命令但不执行,用于检查脚本中的语法错误

-v :一边执行脚本,一边将执行过的脚本命令打印到标准错误输出

-x :提供跟踪执行信息,将执行的每一条命令和结果依次打印出来

  具体使用方法包括以下三种:

  • 命令行参数

    /bin/bash -x ./script.sh
    
  • shebang提供

    #! /bin/bash -x
    
  • set命令启用或禁止调试

    #! /bin/sh
    if [ -z "$1" ]; thenset -xecho "ERROR: Insufficient Args."exit 1set +x
    fi
    

实例

开机自动脚本

  • 你启动脚本复制到 /etc/init.d目录下,假设脚本文件名为 test.sh
  • 修改权限,sudo chmod 755 /etc/init.d/test.sh
  • cd /etc/init.d
  • sudo update-rc.d test.sh defaults 95 (95表示启动顺序,如需用到网络请用较大数字,如99)

登陆自动脚本

  • 修改/etc/motd文件
  • /etc/profile.d文件中加入脚本文件

字符串处理

基本操作

${file-$DEFAULT} # 若file没有声明,则使用$DEFAULT作传回值(空值及非空值时不作处理)
${file:-$DEFAULT} # 若file没有声明或为空值,则使用$DEFAULT作传回值(非空值时不作处理)${file+$DEFAULT} # 若变量已经声明,那么其值是$DEFAULT, 否则是null字符串
${file:+$DEFAULT} # 若变量已经声明且非空,那么其值是$DEFAULT, 否则是null字符串${file=$DEFAULT} # 如果变量没有声明,则$DEFAULT值赋值给变量
${file:=$DEFAULT} # 如果变量没有声明,则$DEFAULT值赋值给变量${file?$DEFAULT} # 如果变量没有声明,则$DEFAULT值输出到STDERR (空值及非空值时不作处理)
${file:?$DEFAULT} # 如果变量没有声明,则$DEFAULT值输出到STDERR(非空值时不作处理)

字符串截取

字符截取
`#`表示去掉左边的字符(单一匹配)
`%`表示去掉右边的字符(单一匹配)
`##`表示去掉左边的字符(贪婪匹配)
`%%`表示去掉右边的字符(贪婪匹配)${file#*/}  # home/jeffery/readme.txt
${file##*/}:# readme.txt
${file#*.}:# md.txt
${file##*.}:# txt
${file%/*}:# /home/jeffery
${file%%/*}:# ''
${file%.*}:# /home/jeffery/readme.md
${file%%.*}:# /home/jeffery/readme
子串截取
# 字符串位置从0开始计数${#file} # 获取字符串的长度
${file:0:5} # 从第0个字符起截取5个字符,/home
${file:5:5} # 从第5个字符起截取5个字符,/jeff# 以下两个表示截取最后四个字符
${file: -4}
${file:(-4)}

字符串替换

${file/str1/str2}  # 将字符串file中的第一个str1替换为str2
${file//dir/path} # 将字符串file中的str1替换为str2

大小写转换

echo "$file"
echo ${file^}
echo ${file^^}
echo ${file,}
echo ${file,,}
echo ${file~}
echo ${file~~}# ^大写,,小写, ~大小写切换, 重复一次只修改首字母,重复两次则应用于所有字母

数组

普通数组

my_array=(A B "C" D) # 定义数组
declare -a my_array2  # 定义数组方法2
my_array2[0]=Becho ${my_array[0]}  # 获取数组元素
echo ${my_array[*]} # 获取所有元素
echo ${my_array[@]} # 获取所有元素echo ${#my_array[*]} # 获取数组长度
echo ${#my_array[@]} # 获取数组长度unset my_array[0]  # 删除第一个元素echo ${my_array[@]:0:2}  # 切片操作,从0开始取值,取两个for v in ${my_array[@]}; do  # 数组遍历echo $v;
donemy_array+=(a b c)  # 往数组中添加元素

关联数组

  关联数组类似于python,C#等语言中的DICTIONARY类型

 declare -A dict  # 定义关联数组dict=([c]=3 [d]=4)  # 赋值dict+=([a]=1 [b]=2)  # 向关联数组添加键值对echo ${!dict[@]} # 获取所有索引echo ${dict[a]}  # 通过key获取value

注意事项

  注意区分以下符号的使用,具体可见上文:

  • 引用变量-----> $DATE${DATE}
  • 命令代换----> `date` 或 $(date)
  • 算术代换---->$(())$[]
  • 逻辑运算符 !-a -o!&& ||
  • 条件测试 [][[]]
       如果a为空,那么[$a -eq 0]会报错,但是[[$a -eq 0]]不会,所以一般都会使用[[]]或者是["$a" -eq 0]

Shell脚本编程详解相关推荐

  1. llinux的shell脚本编程详解

    在Linux系统中,虽然有各种各样的图形化接口工具,但是shell仍然是一个非常灵活的工具.Shell不仅仅是命令的收集,而且是一门非常棒的编程语言.您可以通过使用shell使大量的任务自动化,she ...

  2. windows批处理(cmd/bat脚本)编程详解

    cmd文件和bat文件的区别:二者本质上没有区别,都是简单的文本编码方式,都可以用记事本创建.编辑和查看.两者所用的命令行代码也是共用的,只是cmd文件中允许使用的命令要比bat文件多.cmd文件只有 ...

  3. Spring Boot 项目部署方案 /打包 + Shell 脚本部署详解,稳的一批

    本篇和大家分享的是 Spring Boot 打包并结合 Shell 脚本命令部署,重点在分享一个shell 程序启动工具,希望能便利工作: profiles指定不同环境的配置 maven-assemb ...

  4. linux shc shell脚本_详解shell脚本加密解密软件—gzese和shc

    概述 以我个人的需求为例,有时写一个脚本需要传密码,如果直接把密码写在脚本里会存在安全问题,一般是把密码写在脚本里,作为参数传给脚本,而保存密码的脚本,使用某种手段加密,令其不可读但是可执行.而常用的 ...

  5. 【shell】shell脚本实战-shell脚本函数详解

    文章目录 前言 shell函数的定义 函数的优势 shell 函数的调用 实战案例 总结 前言 Shell 函数的本质是一段可以重复使用的脚本代码,这段代码被提前编写好了,放在了指定的位置,使用时直接 ...

  6. shell脚本编程神器之awk语法案例详解

    AWK入门指南 文章目录 shell脚本编程神器之awk语法案例详解 安装AWK AWK 起步示例 AWK程序的结构 执行 awk 程序 awk 的错误提示 简单输出 打印每一行 打印特定行 NF,字 ...

  7. Linux Shell脚本入门教程系列之(八)Shell printf命令详解

    本文是Linux Shell脚本系列教程的第(八)篇,更多shell教程请看:Linux Shell脚本系列教程 在上一篇:Linux Shell系列教程之(七)Shell输出这篇文章中,已经对She ...

  8. 11. shell当中read详解,read语法选项,read用法示例,脚本示例,while read line详解,掌握原则

    shell当中read详解,read语法选项,read用法示例,脚本示例,while read line详解,掌握原则 文章目录 1.1 read详解 1.2 用法示例 1.3 while read ...

  9. java dtu 采集程序_DTU脚本编程_本地采集脚本指令详解

    前言: 通过配置DTU的脚本指令实现DTU定时自动采集,用户只需知道外接仪表.无需再单独增加控制器传感器的采集流程,然后通过编写脚本指令即可让DTU按照用户的流程自动采集.上传数据.脚本实现了基本的开 ...

最新文章

  1. 求助!!让我郁闷纠结恨的状况!!!
  2. centos7通过yum升级内核到最新版本
  3. java多播_Java多播发送数据,未接收
  4. swagger快速开发
  5. 2011年上海交通大学计算机研究生机试真题
  6. Spark _14_SparkShell的使用
  7. 参数整定临界比例度实验_实验低温浴“秘方”,调节两个溶剂的比例实现零下10度到78度定温配制...
  8. Unity3D Shader 入门之控制语句
  9. java docur,JavaDoc生成API详解
  10. Java基础提高学习笔记1
  11. 200000000021 键盘监听事件
  12. 一个初学者的辛酸路程-Python基础-3
  13. Hadoop学习2:Hadoop环境配置:JDK 1.8和Hadoop配置
  14. 用blender环绕物体拍摄
  15. 共享计算机ip地址,怎么设置局域网计算机IP地址:局域网计算机共享设置
  16. 图像增强—彩色增强技术
  17. OpenGL学习笔记:GLAD和第一个窗口
  18. 微信引流最有效的方法
  19. 博科交换机常用操作命令
  20. (2.0版本)企业微信可信域名,个人添加企业微信可信IP方法

热门文章

  1. eval?python顺序列表模拟栈实现计算器
  2. ActiveMQ实战篇之 java和spring xml创建Broker(一)
  3. 10. 考点概览和摘要
  4. python求阶乘和
  5. [leetcode] 105.从前序与中序遍历构造二叉树
  6. dbref java_java – Spring Data REST MongoDB:检索DBRef的对...
  7. Linux网络模式及远程连接出错排障
  8. 从实验室到山寨,技术发展的普遍道路
  9. redis 连接池_SpringBoot整合redis
  10. Java自动部署maven_Maven+Tomcat8 实现自动化部署的方法