精华内容
下载资源
问答
  • 收藏!篇教会你写90%的shell脚本

    万次阅读 多人点赞 2019-09-06 18:18:36
    还不会写shell脚本?这篇文章教会你写90%的shell脚本

    在公司项目的开发过程中,需要编写shell脚本去处理一个业务,在编写过程中发现自身对shell脚本的知识不够完善,顾整理一下,本文章主要内容来自菜鸟教程 , 也添加了一些知识点

    看完这边文章应该就可以独立完成大部分脚本得编写

    shell脚本?

    在说什么是shell脚本之前,先说说什么是shell。

    shell是外壳的意思,就是操作系统的外壳。我们可以通过shell命令来操作和控制操作系统,比如Linux中的Shell命令就包括ls、cd、pwd等等。总结来说,Shell是一个命令解释器,它通过接受用户输入的Shell命令来启动、暂停、停止程序的运行或对计算机进行控制。

    shell 是一个应用程序,它连接了用户和 Linux 内核,让用户能够更加高效、安全、低成本地使用 Linux 内核,这就是 Shell 的本质。

    shell 本身并不是内核的一部分,它只是站在内核的基础上编写的一个应用程序。

    那么什么是shell脚本呢?

    shell脚本就是由Shell命令组成的执行文件,将一些命令整合到一个文件中,进行处理业务逻辑,脚本不用编译即可运行。它通过解释器解释运行,所以速度相对来说比较慢。

    shell脚本中最重要的就是对shell命令的使用与组合,再使用shell脚本支持的一些语言特性,完成想要的功能。

    微信扫下述二维码关注公众号,回复“源码”,即可免费获取“经典飞机大战源码 + 经典像素鸟游戏源码 + 人事管理系统源码 ”!
    匠心Java*

    注释

    “# ”开头的就是注释,被编译器忽略

    • 单行注释: #
    • 多行注释: :<<EOF … EOF 或者 :<<! … ! (:<< 标识多行注释开始,并指定一个标识符作为开始结束的标志)

    变量

    变量类型
    运行shell时,会同时存在三种变量:

    1. 局部变量:局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。
    2. 环境变量:所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。
    3. shell变量:shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行

    变量操作

    • 创建普通变量: name=“test” (=两边不可有空格)
    • 创建只可函数体中使用的局部变量: local name=“test” (使用local修饰的变量在函数体外无法访问,并且local只能在函数体内使用)
    • 使用变量: echo $name 或者 echo ${name} (推荐使用大括号版)
    • 变量重新赋值: name=“new_test” (将原值覆盖)
    • 只读变量: name=“only_read” -> readonly name (使用readonly标识后的变量,不可被修改)
    • 删除变量: unset name; (删除之后不可访问,删除不掉只读变量)

    字符串变量
    1)单引号

    • 单引号变量var='test' ,只能原样输出,变量无效
    • 单引号中不能出现一个单独的单引号,转义也不可以

    2)双引号

    • 双引号变量var="my name is ${name}",变量有效
    • 可出现转义符

    3)拼接字符串

    • 中间无任何+,之类的字符
    • name=“this is”" my name"; name=“this is my name”; name=“this” is “my name” 等效
    • name=‘this is’’ my nam’; name=‘this is my name’; name=‘this’ is ‘my name’ 等效

    4)获取字符串长度

    • 在${}中使用“#”获取长度
    • name=“test”;
    • echo ${#name}; # 输出为4

    5)提取子字符串

    • 1:4 从第2个开始 往后截取4个字符
    • ::4 从第一个字符开始 往后截取4个字符
    • name=“this is my name”;
    • echo ${name:1:4} #输出 his
    • echo ${name::4} #输出 this

    数组

    bash只支持一维数组,不支持多维数组

    • 定义数组:array_name=(li wang xiang zhang) (小括号做边界、使用空格分离)
    • 单独定义数组的元素: array_para[0]=“w”; array_para[3]=“s” (定义时下标不连续也可以)
    • 赋值数组元素:array_name[0]=“zhao”;
    • 获取数组元素:
      • array_name[0]=“li”
      • array_name[3]=“zhang”
      • echo ${array_name[0]} # 输出"li"
      • echo ${array_name[1]} # 输出" "
      • echo ${array_name[3]} # 输出"zhang"
      • echo ${array_name[@]} # 输出"li zhang" 输出数组所有元素,没有元素的下标省略
    • 取得元素个数:${#array_name[@]} 或者 ${#array_name[*]}
    • 取得单个元素长度:${#array_name[1]}

    参数传递

    • 获取参数值:
      • $0 : 固定,代表执行的文件名
      • $1 : 代表传入的第1个参数
      • $n : 代表传入的第n个参数
    • $#:参数个数
    • $*: 以一个单字符串显示所有向脚本传递的参数。如"$*“用「”」括起来的情况、以"$1 $2 … $n"的形式输出所有参数
    • $@:与$*相同,但是使用时加引号,并在引号中返回每个参数。
    • $$:脚本运行的当前进程号
    • $!:后台运行的最后一个进程的ID
    • $?: 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。
    • $* 与 $@ 区别
      • 相同点:都是引用所有参数。
      • 不同点:只有在双引号中体现出来。假设在脚本运行时写了三个参数 1、2、3,,则 " * " 等价于 “1 2 3”(传递了一个参数),而 “@” 等价于 “1” “2” “3”(传递了三个参数)。

    运算符

    算数运算

    • + 、-、*、\ : 乘号前必须加\进行转义才可以进行乘法运算
    • 加法运算
      • val=`expr 2 + 2` (使用linux命令expr进行辅助运算)
      • val=$[2+2] (4个空格不是必要的,不同于条件判断)
      • val=$((2+2))

    数字关系运算符
    关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
    下面假定变量 a 为 10,变量 b 为 20

    • -eq :检测两个数是否相等,相等返回 true。 [ $a -eq $b ] 返回 false。
    • -ne: 检测两个数是否不相等,不相等返回 true。 [ $a -ne $b ] 返回 true。
    • -gt: 检测左边的数是否大于右边的,如果是,则返回 true。 [ $a -gt $b ] 返回 false。
    • -lt : 检测左边的数是否小于右边的,如果是,则返回 true。 [ $a -lt $b ] 返回 true。
    • -ge: 检测左边的数是否大于等于右边的,如果是,则返回 true。 [ $a -ge $b ] 返回 false。
    • -le : 检测左边的数是否小于等于右边的,如果是,则返回 true。 [ $a -le $b ] 返回 true。

    字符串运算符
    下表列出了常用的字符串运算符,假定变量 a 为 “abc”,变量 b 为 “efg”:

    • = :检测两个字符串是否相等,相等返回 true。 [ $a = $b ] 返回 false。
    • != :检测两个字符串是否相等,不相等返回 true。 [ $a != $b ] 返回 true。
    • -z :检测字符串长度是否为0,为0返回 true。 [ -z $a ] 返回 false。
    • -n :检测字符串长度是否为0,不为0返回 true。 [ -n “$a” ] 返回 true。
    • $ :检测字符串是否为空,不为空返回 true。 [ $a ] 返回 true。

    布尔运算符
    下表列出了常用的布尔运算符,假定变量 a 为 10,变量 b 为 20:

    • ! :非运算,表达式为 true 则返回 false,否则返回 true。 [ ! false ] 返回 true。
    • -o :或运算,有一个表达式为 true 则返回 true。 [ $a -lt 20 -o $b -gt 100 ] 返回 true。
    • -a :与运算,两个表达式都为 true 才返回 true。 [ $a -lt 20 -a $b -gt 100 ] 返回 false。

    逻辑运算符
    以下介绍 Shell 的逻辑运算符,假定变量 a 为 10,变量 b 为 20:

    • && :逻辑的 AND [[ $a -lt 100 && $b -gt 100 ]] 返回 false
    • || :逻辑的 OR [[ $a -lt 100 || $b -gt 100 ]] 返回 true

    文件运算符

    • -b file :检测文件是否是块设备文件,如果是,则返回 true。 [ -b $file ] 返回 false。
    • -c file :检测文件是否是字符设备文件,如果是,则返回 true。 [ -c $file ] 返回 false。
    • -d file :检测文件是否是目录,如果是,则返回 true。 [ -d $file ] 返回 false。
    • -f file :检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。 [ -f $file ] 返回 true。
    • -g file :检测文件是否设置了 SGID 位,如果是,则返回 true。 [ -g $file ] 返回 false。
    • -k file :检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。 [ -k $file ] 返回 false。
    • -p file :检测文件是否是有名管道,如果是,则返回 true。 [ -p $file ] 返回 false。
    • -u file :检测文件是否设置了 SUID 位,如果是,则返回 true。 [ -u $file ] 返回 false。
    • -r file :检测文件是否可读,如果是,则返回 true。 [ -r $file ] 返回 true。
    • -w file :检测文件是否可写,如果是,则返回 true。 [ -w $file ] 返回 true。
    • -x file :检测文件是否可执行,如果是,则返回 true。 [ -x $file ] 返回 true。
    • -s file :检测文件是否为空(文件大小是否大于0),不为空返回 true。 [ -s $file ] 返回 true。
    • -e file :检测文件(包括目录)是否存在,如果是,则返回 true。 [ -e $file ] 返回 true。

    执行相关

    命令替换
    命令替换与变量替换差不多,都是用来重组命令行的,先完成引号里的命令行,然后将其结果替换出来,再重组成新的命令行。
    执行命令:

    1. `ls /etc` : 反引号 (所有的unix系统都支持)
    2. $(ls /etc) : $+() (部分unix系统不支持)
      多个嵌套使用时,从内向外执行

    for file in \s /etc\ 或 for file in $(ls /etc) 循环中使用
    `dirname $0` 获取脚本文件所在的目录
    path=$(cd `dirname $0`;pwd) : 获取脚本当前所在目录,并且执行cd命令到达该目录,使用pwd获取路径并赋值到path变量

    算术运算

    1. $[ ] : 加减乘除,不必添加空格
    2. $(( )) :加减乘除等,不必添加空格

    逻辑判断

    1. [ ] : 中括号旁边和运算符两边必须添加空格 (可以使用,不推荐)
    2. [[ ]]:中括号旁边和运算符两边必须添加空格 (字符串验证时,推荐使用)
    3. (()) : 中括号旁边和运算符两边必须添加空格 (数字验证时,推荐使用)
    4. [[]] 和 (()) 分别是[ ]的针对数学比较表达式和字符串表达式的加强版。
    5. 使用[[ … ]]条件判断结构,而不是[ … ],能够防止脚本中的许多逻辑错误。比如,&&、||、<和> 操作符能够正常存在于[[ ]]条件判断结构中,但是如果出现在[ ]结构中的话,会报错。比如可以直接使用if [[ $a != 1 && $a != 2 ]], 如果不适用双括号, 则为if [ $a -ne 1] && [ $a != 2 ]或者if [ $a -ne 1 -a $a != 2 ]。
      [[ ]]中增加模式匹配特效;
      (( ))不需要再将表达式里面的大小于符号转义,除了可以使用标准的数学运算符外,还增加了以下符号
      在这里插入图片描述

    输出

    echo
    仅用于字符串的输出,没有使用printf作为输出的移植性好,建议使用printf

    printf

    printf 不会像 echo 自动添加换行符,我们可以手动添加 \n
    无大括号,直接以空格分隔

    • 格式:printf format-string [arguments...] 其中(format-string: 格式控制字符串、arguments: 参数列表)
    • 案例:printf "%-10s %-8s %-4.2f\n" 郭靖 男 66.1234
    • %s %c %d %f 都是格式替代符
      • d:Decimal 十进制整数 对应位置参数必须是十进制整数,否则报错!
      • s:String 字符串 对应位置参数必须是字符串或者字符型 否则报错
      • c:Char 字符 对应位置参数必须是字符串或者字符型 否则报错
      • f:Float 浮点 对应位置参数必须是数字型 否则报错
    • %-10s : 指一个宽度为10个字符(-表示左对齐,没有则表示右对齐),任何字符都会被显示在10个字符宽的字符内,如果不足则自动以空格填充,超过也会将内容全部显示出来。
    • %-4.2f :指格式化为小数,宽度为4个字符,其中.2指保留2位小数。
    • 转义符:
      • \a :警告字符,通常为ASCII的BEL字符
      • \b :后退
      • \c :抑制(不显示)输出结果中任何结尾的换行字符(只在%b格式指示符控制下的参数字符串中有效),而且,任何留在参数里的字符、任何接下来的参数以及任何留在格式字符串中的字符,都被忽略
      • \f :换页(formfeed)
      • \n :换行
      • \r :回车(Carriage return)
      • \t :水平制表符
      • \v :垂直制表符
      • \ :一个字面上的反斜杠字符
      • \ddd :表示1到3位数八进制值的字符。仅在格式字符串中有效
      • \0ddd :表示1到3位的八进制值字符

    流程控制

    和Java、PHP等语言不一样,sh的流程控制不可为空,即if或者else的大括号中无任何语句
    if else

    • if
    if condition
    then
        command1 
        command2
        ...
        commandN 
    fi
    
    • if else
    if condition
    then
        command1 
        command2
        ...
        commandN
    else
        command
    fi
    
    • if else-if else
    if condition1
    then
        command1
    elif condition2 
    then 
        command2
    else
        commandN
    fi
    

    for

    for var in item1 item2 ... itemN
    do
        command1
        command2
        ...
        commandN
    done
    

    while

    • while condition
    while condition
    do
        command
    done
    
    • while 无限循环
    while :
    do
        command
    done
    

    until
    until 循环执行一系列命令直至条件为 true 时停止。
    until 循环与 while 循环在处理方式上刚好相反。

    until condition
    do
        command
    done
    

    case
    Shell case语句为多选择语句。可以用case语句匹配一个值与一个模式,如果匹配成功,执行相匹配的命令。
    case需要一个esac(就是case反过来)作为结束标记,每个case分支用右圆括号,用两个分号表示break,其中“;;”不是跳出循环,是不在去匹配下面的模式
    case语句格式如下:

    casein
      模式1)
        command1
        command2
        ...
        commandN
        ;;
      模式2)
        command1
        command2
        ...
        commandN
        ;;
    esac
    

    跳出循环

    1. break :跳出总循环
    2. continue:跳出当前循环,继续下一次循环

    定义函数

    可以带function fun() 定义,也可以直接fun() 定义,不带任何参数。

    • 函数定义
    [ function ] funname()
    {
        action;
        [return int;]
    }
    
    • 参数传递
      • 调用函数: fun_name 2 3 4
      • 函数中使用:和shell取用函数相同 $n $# $* $? 或者加上{}
    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
    echo $?  \# 判断执行是否成功
    
    • 函数返回值
      • return字样可存在也可不存在
      • return 只能为 return [0-255],此处的返回可作为函数执行的状态,通过$?获取的便是这个返回值
      • 如果不加return , 则默认最后一条语句的执行状态所为函数执行状态的返回值,如果最后一条语句执行成功,则$?为0,否则不为0
    • 使用函数返回值(Janusgraph图数据库官方启动服务脚本片段)
      • return返回的数字,只是作为函数执行状态的返回值,也就是接下来$?获取的值
      • 对于类似于下面的BIN=\abs_path``语句,获取的是函数体内所有的echo、printf输出组合成的一个字符串
    abs_path() {
        SOURCE="${BASH_SOURCE[0]}"
        while [ -h "$SOURCE" ]; do
            DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
            SOURCE="$(readlink "$SOURCE")"
            [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
        done
        echo "test"  
        echo "$( cd -P "$( dirname "$SOURCE" )" && pwd )"  
        # 此函数的两个echo输出会组合成一个字符串作为下述BIN的值
    }
    
    BIN=`abs_path` # BIN赋值函数返回值,如果没有return,则函数中所有的echo、printf输出组合成一个字符串传入BIN
    path=${BIN}/nodetool # 可直接使用
    

    输入输出重定向

    一般情况下,每个 Unix/Linux 命令运行时都会打开三个文件:

    • 标准输入文件(stdin):stdin的文件描述符为0,Unix程序默认从stdin读取数据。
    • 标准输出文件(stdout):stdout 的文件描述符为1,Unix程序默认向stdout输出数据。
    • 标准错误文件(stderr):stderr的文件描述符为2,Unix程序会向stderr流中写入错误信息。

    默认情况下,command > file 将 stdout 重定向到 file,command < file 将stdin 重定向到 file。
    如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null:

    输入重定向

    1. bash.sh < file : 将脚本的输入重定向到file,由file提供参数

    输出重定向

    1. bash.sh > file : 将脚本的输出数据重定向到file中,覆盖数据
    2. bash.sh >> file : 将脚本的输出数据重定向到file中,追加数据
    3. command >> file 2>&1 : 将 stdout 和 stderr 合并后重定向到 file

    读取外部输入

    命令:read arg (脚本读取外部输入并赋值到变量上)
    在shell脚本执行到上述命令时,停止脚本执行并等待外部输入,将外部输入赋值到arg变量上,继续执行脚本

    文件引用

    引用其他的文件之后,可以使用其变量、函数等等,相当于将引用的文件包含进了当前文件
    两种方式:

    1. . file_path\file_name
    2. source file_path\file_name

    颜色标识

    printf  "\033[32m SUCCESS: yay \033[0m\n";
    printf  "\033[33m WARNING: hmm \033[0m\n";
    printf  "\033[31m ERROR: fubar \033[0m\n";
    

    输出结果:
    在这里插入图片描述

    长句换行

    在shell中为避免一个语句过长,可以使用“\”进行换行
    使用“\”换行,在脚本执行过程中还是当做一行一个语句执行,不同于enter直接换行

    注意:\ 前添加一个空格 。 \ 后无空格直接换行。

     /mysql/bin/mysql \
      -h test_host  -P 000 \
      -u test_user -ptest_password ;
    

    shell操作mysql

    下面案例为登录mysql,并选择操作数据库,之后进行导入数据

     /mysql/mysql/bin/mysql \
      -h test_host  -P 000 \
      -u test_user -ptest_password \
      -e"use test_database; source data_faile; " # -e 代表执行sql语句
    

    -u 用户名
    -p 用户密码
    -h 服务器ip地址
    -D 连接的数据库
    -N 不输出列信息
    -B 使用tab键 代替 分隔符
    -e 执行的SQL语句

    退出脚本

    命令:exit

    在退出脚本时使用不同的错误码,这样可以根据错误码来判断发生了什么错误。

    在绝大多数 shell 脚本中,exit 0 表示执行成功,exit 1 表示发生错误。
    对错误与错误码进行一对一的映射,这样有助于脚本调试。

    命令:set -e 或者 set +e
    set -e表示从当前位置开始,如果出现任何错误都将触发exit。相反,set +e表示不管出现任何错误继续执行脚本。

    如果脚本是有状态的(每个后续步骤都依赖前一个步骤),那么请使用set -e,在脚本出现错误时立即退出脚本。
    如果要求所有命令都要执行完(很少会这样),那么就使用set +e。

    shell脚本调试

    检查是否有语法错误-n
    bash -n script_name.sh
    使用下面的命令来执行并调试 Shell 脚本-x
    bash -x script_name.sh
    调试count_odd_number.sh 程序案例:

    #!/usr/bin.env bash
    
    # 用于计算数组中奇数的和
    # @author liyangyang
    # @time 2019/09/17
    
    sum=0
    for num in 1 2 3 4;do
        re=${num}%2
        if (( ${re} == 1 ));then
            sum=$[${sum}+${num}]
        fi
    done
    echo ${sum}
    
    1. 首先检查有无语法错误:
      bash -n count_odd_number.sh
    2. 没有输出,说明没有错误,开始实际调试:
      bash -x count_odd_number.sh
    3. 调试结果如下:
    + sum=0
    + for num in 1 2 3 4
    + re=1%2
    + ((  1%2 == 1  ))
    + sum=1
    + for num in 1 2 3 4
    + re=2%2
    + ((  2%2 == 1  ))
    + for num in 1 2 3 4
    + re=3%2
    + ((  3%2 == 1  ))
    + sum=4
    + for num in 1 2 3 4
    + re=4%2
    + ((  4%2 == 1  ))
    + echo 4
    4
    

    其中的输出显示了程序执行的每一步,通过观察程序执行的步骤是否满足预期从而达到调试的效果
    带有 + 表示的是 Shell 调试器的输出,不带 + 表示程序的输出。

    案例:

    这是es(ElasticSearch)官方启动服务的脚本,看可不可以理解吧~

    #!/usr/bin/env bash
    
    # CONTROLLING STARTUP:
    #
    # This script relies on a few environment variables to determine startup
    # behavior, those variables are:
    #
    #   ES_PATH_CONF -- Path to config directory
    #   ES_JAVA_OPTS -- External Java Opts on top of the defaults set
    #
    # Optionally, exact memory values can be set using the `ES_JAVA_OPTS`. Note that
    # the Xms and Xmx lines in the JVM options file must be commented out. Example
    # values are "512m", and "10g".
    #
    #   ES_JAVA_OPTS="-Xms8g -Xmx8g" ./bin/elasticsearch
    
    source "`dirname "$0"`"/elasticsearch-env
    
    parse_jvm_options() {
      if [ -f "$1" ]; then
        echo "`grep "^-" "$1" | tr '\n' ' '`"
      fi
    }
    
    ES_JVM_OPTIONS="$ES_PATH_CONF"/jvm.options
    
    ES_JAVA_OPTS="`parse_jvm_options "$ES_JVM_OPTIONS"` $ES_JAVA_OPTS"
    
    # manual parsing to find out, if process should be detached
    if ! echo $* | grep -E '(^-d |-d$| -d |--daemonize$|--daemonize )' > /dev/null; then
      exec \
        "$JAVA" \
        $ES_JAVA_OPTS \
        -Des.path.home="$ES_HOME" \
        -Des.path.conf="$ES_PATH_CONF" \
        -cp "$ES_CLASSPATH" \
        org.elasticsearch.bootstrap.Elasticsearch \
        "$@"
    else
      exec \
        "$JAVA" \
        $ES_JAVA_OPTS \
        -Des.path.home="$ES_HOME" \
        -Des.path.conf="$ES_PATH_CONF" \
        -cp "$ES_CLASSPATH" \
        org.elasticsearch.bootstrap.Elasticsearch \
        "$@" \
        <&- &
      retval=$?
      pid=$!
      [ $retval -eq 0 ] || exit $retval
      if [ ! -z "$ES_STARTUP_SLEEP_TIME" ]; then
        sleep $ES_STARTUP_SLEEP_TIME
      fi
      if ! ps -p $pid > /dev/null ; then
        exit 1
      fi
      exit 0
    fi
    
    exit $?
    

    推荐阅读:
    Git-【技术干货】工作中Git的使用实践
    Git - 使用git不知道内部实现机制怎么行

    如果转载此博文,请附上本文链接,谢谢合作~ :https://blog.csdn.net/CSDN___LYY/article/details/100584638

    如果感觉这篇文章对您有所帮助,请点击一下“喜欢”或者“关注”博主,您的喜欢和关注将是我前进的最大动力!谢谢大家~

    在这里插入图片描述

    展开全文
  • 编写第一个shell脚本

    千次阅读 2020-10-23 09:16:44
    在前面的章节中,我们已经装备了一个命令行工具的武器库。虽然这些工具能够解决许多种计算问题, 但是我们仍然局限于在命令行...最简单的解释,一个 shell 脚本就是一个包含一系列命令的文件。shell 读取这个文件,然后

    在前面的章节中,我们已经装备了一个命令行工具的武器库。虽然这些工具能够解决许多种计算问题, 但是我们仍然局限于在命令行中手动地一个一个使用它们。难道不是很棒,如果我们能够让 shell 来完成更多的工作? 我们可以的。通过把我们的工具一起放置到我们自己设计的程序中,然后 shell 就会自己来执行这些复杂的任务序列。 通过编写 shell 脚本,我们让 shell 来做这些事情。

    什么是 Shell 脚本?

    最简单的解释,一个 shell 脚本就是一个包含一系列命令的文件。shell 读取这个文件,然后执行 文件中的所有命令,就好像这些命令已经直接被输入到了命令行中一样。

    Shell 有些独特,因为它不仅是一个功能强大的命令行接口,也是一个脚本语言解释器。我们将会看到, 大多数能够在命令行中完成的任务也能够用脚本来实现,同样地,大多数能用脚本实现的操作也能够 在命令行中完成。

    虽然我们已经介绍了许多 shell 功能,但只是集中于那些经常直接在命令行中使用的功能。 Shell 也提供了一些通常(但不总是)在编写程序时才使用的功能。

    怎样编写一个 Shell 脚本

    为了成功地创建和运行一个 shell 脚本,我们需要做三件事情:

    1. 编写一个脚本。 Shell 脚本就是普通的文本文件。所以我们需要一个文本编辑器来书写它们。最好的文本 编辑器都会支持语法高亮,这样我们就能够看到一个脚本关键字的彩色编码视图。语法高亮会帮助我们查看某种常见 错误。为了编写脚本文件,vim,gedit,kate,和许多其它编辑器都是不错的候选者。

    2. 使脚本文件可执行。 系统会相当挑剔不允许任何旧的文本文件被看作是一个程序,并且有充分的理由! 所以我们需要设置脚本文件的权限来允许其可执行。

    3. 把脚本放置到 shell 能够找到的地方 当没有指定可执行文件明确的路径名时,shell 会自动地搜索某些目录, 来查找此可执行文件。为了最大程度的方便,我们会把脚本放到这些目录当中。

    脚本文件格式

    为了保持编程传统,我们将创建一个 “hello world” 程序来说明一个极端简单的脚本。所以让我们启动 我们的文本编辑器,然后输入以下脚本:

    #!/bin/bash
    # This is our first script.
    echo 'Hello World!'
    
    

    对于脚本中的最后一行,我们应该是相当的熟悉,仅仅是一个带有一个字符串参数的 echo 命令。 对于第二行也很熟悉。它看起来像一个注释,我们已经在许多我们检查和编辑过的配置文件中 看到过。关于 shell 脚本中的注释,它们也可以出现在文本行的末尾,像这样:

    echo 'Hello World!' # This is a comment too
    
    

    文本行中,# 符号之后的所有字符都会被忽略。

    类似于许多命令,这也在命令行中起作用:

    [me@linuxbox ~]$ echo 'Hello World!' # This is a comment too
    Hello World!
    
    

    虽然很少在命令行中使用注释,但它们也能起作用。

    我们脚本中的第一行文本有点儿神秘。它看起来它应该是一条注释,因为它起始于一个#符号,但是 它看起来太有意义,以至于不仅仅是注释。事实上,这个#!字符序列是一种特殊的结构叫做 shebang。 这个 shebang 被用来告诉操作系统将执行此脚本所用的解释器的名字。每个 shell 脚本都应该把这一文本行 作为它的第一行。

    让我们把此脚本文件保存为 hello_world。

    可执行权限

    下一步我们要做的事情是让我们的脚本可执行。使用 chmod 命令,这很容易做到:

    [me@linuxbox ~]$ ls -l hello_world
    -rw-r--r-- 1  me    me      63  2009-03-07 10:10 hello_world
    [me@linuxbox ~]$ chmod 755 hello_world
    [me@linuxbox ~]$ ls -l hello_world
    -rwxr-xr-x 1  me    me      63  2009-03-07 10:10 hello_world
    
    

    对于脚本文件,有两个常见的权限设置;权限为755的脚本,则每个人都能执行,和权限为700的 脚本,只有文件所有者能够执行。注意为了能够执行脚本,脚本必须是可读的。

    脚本文件位置

    当设置了脚本权限之后,我们就能执行我们的脚本了:

    [me@linuxbox ~]$ ./hello_world
    Hello World!
    
    

    为了能够运行此脚本,我们必须指定脚本文件明确的路径。如果我们没有那样做,我们会得到这样的提示:

    [me@linuxbox ~]$ hello_world
    bash: hello_world: command not found
    
    

    为什么会这样呢?什么使我们的脚本不同于其它的程序?结果证明,什么也没有。我们的 脚本没有问题。是脚本存储位置的问题。回到第12章,我们讨论了 PATH 环境变量及其它在系统 查找可执行程序方面的作用。回顾一下,如果没有给出可执行程序的明确路径名,那么系统每次都会 搜索一系列的目录,来查找此可执行程序。这个/bin 目录就是其中一个系统会自动搜索的目录。 这个目录列表被存储在一个名为 PATH 的环境变量中。这个 PATH 变量包含一个由冒号分隔开的目录列表。 我们可以查看 PATH 的内容:

    [me@linuxbox ~]$ echo $PATH
    /home/me/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:
    /bin:/usr/games
    
    

    这里我们看到了我们的目录列表。如果我们的脚本驻扎在此列表中任意目录下,那么我们的问题将 会被解决。注意列表中的第一个目录,/home/me/bin。大多数的 Linux 发行版会配置 PATH 变量,让其包含 一个位于用户家目录下的 bin 目录,从而允许用户能够执行他们自己的程序。所以如果我们创建了 一个 bin 目录,并把我们的脚本放在这个目录下,那么这个脚本就应该像其它程序一样开始工作了:

    [me@linuxbox ~]$ mkdir bin
    [me@linuxbox ~]$ mv hello_world bin
    [me@linuxbox ~]$ hello_world
    Hello World!
    
    

    它的确工作了。

    如果这个 PATH 变量不包含这个目录,我们能够轻松地添加它,通过在我们的.bashrc 文件中包含下面 这一行文本:

    export PATH=~/bin:"$PATH"
    
    

    当做了这个修改之后,它会在每个新的终端会话中生效。为了把这个修改应用到当前的终端会话中, 我们必须让 shell 重新读取这个 .bashrc 文件。这可以通过 “sourcing”.bashrc 文件来完成:

    [me@linuxbox ~]$ . .bashrc
    
    

    这个点(.)命令是 source 命令的同义词,一个 shell 内部命令,用来读取一个指定的 shell 命令文件, 并把它看作是从键盘中输入的一样。


    注意:在 Ubuntu 系统中,如果存在 ~/bin 目录,当执行用户的 .bashrc 文件时, Ubuntu 会自动地添加这个 ~/bin 目录到 PATH 变量中。所以在 Ubuntu 系统中,如果我们创建 了这个 ~/bin 目录,随后退出,然后再登录,一切会正常运行。


    脚本文件的好去处

    这个 ~/bin 目录是存放为个人所用脚本的好地方。如果我们编写了一个脚本,系统中的每个用户都可以使用它, 那么这个脚本的传统位置是 /usr/local/bin。系统管理员使用的脚本经常放到 /usr/local/sbin 目录下。 大多数情况下,本地支持的软件,不管是脚本还是编译过的程序,都应该放到 /usr/local 目录下, 而不是在 /bin 或 /usr/bin 目录下。这些目录都是由 Linux 文件系统层次结构标准指定,只包含由 Linux 发行商 所提供和维护的文件。

    更多的格式技巧

    严肃认真的脚本书写,一个关键目标是为了维护方便;也就是说,一个脚本可以轻松地被作者或其它 用户修改,使它适应变化的需求。使脚本容易阅读和理解是一种方便维护的方法。

    长选项名称

    我们学过的许多命令都以长短两种选项名称为特征。例如,这个 ls 命令有许多选项既可以用短形式也 可以用长形式来表示。例如:

    [me@linuxbox ~]$ ls -ad
    
    

    和:

    [me@linuxbox ~]$ ls --all --directory
    
    

    是等价的命令。为了减少输入,当在命令行中输入选项的时候,短选项更受欢迎,但是当书写脚本的时候, 长选项能提供可读性。

    缩进和行继续符

    当雇佣长命令的时候,通过把命令在几个文本行中展开,可以提高命令的可读性。 在第十八章中,我们看到了一个特别长的 find 命令实例:

    [me@linuxbox ~]$ find playground \( -type f -not -perm 0600 -exec
    chmod 0600 ‘{}’ ‘;’ \) -or \( -type d -not -perm 0711 -exec chmod
    0711 ‘{}’ ‘;’ \)
    
    

    显然,这个命令有点儿难理解,当第一眼看到它的时候。在脚本中,这个命令可能会比较容易 理解,如果这样书写它:

    find playground \
        \( \
            -type f \
            -not -perm 0600 \
            -exec chmod 0600 ‘{}’ ‘;’ \
        \) \
        -or \
        \( \
            -type d \
            -not -perm 0711 \
            -exec chmod 0711 ‘{}’ ‘;’ \
        \)
    
    

    通过使用行继续符(反斜杠-回车符序列)和缩进,这个复杂命令的逻辑性更清楚地描述给读者。 这个技巧在命令行中同样生效,虽然很少使用它,因为输入和编辑这个命令非常麻烦。脚本和 命令行的一个区别是,脚本可能雇佣 tab 字符拉实现缩进,然而命令行却不能,因为 tab 字符被用来 激活自动补全功能。

    为书写脚本配置 vim

    这个 vim 文本编辑器有许多许多的配置设置。有几个常见的选项能够有助于脚本书写:

    :syntax on

    打开语法高亮。通过这个设置,当查看脚本的时候,不同的 shell 语法元素会以不同的颜色 显示。这对于识别某些编程错误很有帮助。并且它看起来也很酷。注意为了这个功能起作用,你 必须安装了一个完整的 vim 版本,并且你编辑的文件必须有一个 shebang,来说明这个文件是 一个 shell 脚本。如果对于上面的命令,你遇到了困难,试试 :set syntax=sh。

    :set hlsearch

    打开这个选项是为了高亮查找结果。比如说我们查找单词“echo”。通过设置这个选项,这个 单词的每个实例会高亮显示。

    :set tabstop=4

    设置一个 tab 字符所占据的列数。默认是8列。把这个值设置为4(一种常见做法), 从而让长文本行更容易适应屏幕。

    :set autoindent

    打开 “auto indent” 功能。这导致 vim 能对新的文本行缩进与刚输入的文本行相同的列数。 对于许多编程结构来说,这就加速了输入。停止缩进,输入 Ctrl-d。

    通过把这些命令(没有开头的冒号字符)添加到你的 ~/.vimrc 文件中,这些改动会永久生效。

    总结归纳

    在这脚本编写的第一章中,我们已经看过怎样编写脚本,怎样让它们在我们的系统中轻松地执行。 我们也知道了怎样使用各种格式技巧来提高脚本的可读性(可维护性)。在以后的各章中,轻松维护 会作为编写好脚本的中心法则一次又一次地出现。

    拓展阅读

    展开全文
  • Shell脚本

    万次阅读 2020-10-04 20:29:52
    Shell脚本 这里写目录标题Shell脚本1.Shell脚本简介2.Shell2.1 Shell 脚本的解释器2.2 脚本的编写规范2.3 执行脚本的三种方式2.4变量...Shell 是一个用 C 语言编写的程序,通过 Shell 用户可以访问操作系统内核服务。

    Shell脚本


    1.Shell脚本简介

    Shell 是一个用 C 语言编写的程序,通过 Shell 用户可以访问操作系统内核服务。Shell 既是一种命令语言,又是一种程序设计语言。Shell script 是一种为 shell 编写的脚本程序。Shell 编程一般指 shell脚本编程,不是指开发 shell 自身。Shell 编程跟 java、php 编程一样,只要有一个能编写代码的文本编辑器和一个能解释执行的脚本解释器就可以了。Linux 的 Shell 解释器 种类众多,一个系统可以存在多个 shell,可以通过 cat /etc/shells 命令查看系统中安装的 shell解释器。Bash 由于易用和免费,在日常工作中被广泛使用。同时,Bash 也是大多数Linux 系统默认的 Shell。

    2.Shell

    2.1 Shell 脚本的解释器

    /bin/sh
    /bin/bash
    /sbin/nologin
    /bin/dash
    /bin/tcsh
    /bin/csh
    

    2.2 脚本的编写规范

    首行描述解释器 #!/bin/bash

    2.3 执行脚本的三种方式

    1、bash 脚本名称 脚本参数
    2、 sh 脚本名称 脚本参数
    3、 ./脚本名称 脚本参数【./脚本名称之间没有空格】【chmod 777 脚本名称】

    2.4变量的声明

    变量名=”值”【中间不能有空格】
    变量名要求 字母或下划线或数字(开头不能是数字)构成

    2.5变量的引用

    使用$ 或 ${}引用变量

    2.6删除变量 只读变量

    删除 unset 变量名
    只读 readonly 变量名

    2.7字符串使用

    双引号(””):内部可以使用变量
    单引号(‘’):内部所有的原样输出
    反引号(``):表示可执行

    2.8字符串长度及截取

    长度: echo ${#skill}
    截取: echo ${str:2:2} 【在第二个字符开始截取截取两位】

    2.9脚本参数传递

    bash 脚本名 参数1 参数2 参数3 。。。。。。
    脚本内
    $0 脚本名称
    $1 参数1
    $2 参数2
    $n 参数n
    $* 所有参数
    $# 参数的个数

    2.10算数运算符

    加法计算 expr 2 + 2
    乘法计算 expr 2 \* 2

    2.11流程控制if

    if [ $a -gt 10 ];then
    代码1
    elif [ $a -lt 10 ];then
    代码2
    else 代码3
    fi

    2.12关系运算符

    在这里插入图片描述

    2.13循环

    For 循环
    For aa in XXXX
    do
    循环体
    done

    While循环
    While 条件 ;
    do
    循环体
    done

    跳出循环
    跳出所有循环 break
    跳出当前循环,进入下一次循环 continue

    case ()

    case 值 in
    结果1) 结果1对应的操作;;
    结果2) 结果2对应的操作;;
    结果3) 结果3对应的操作;;
    esac

    2.14接收控制台数据

    read 变量名
    函数的使用
    函数声明:
    [function] 函数名(){
    函数体
    $参数1
    $参数2
    $参数3
    }

    函数调用 函数名 参数1 参数2 参数3

    2.15数组

    数组声明: 数组名称=(元素1 元素2 元素3 元素4)
    数组的使用
    单个使用 echo 数组名[角标] 【角标从0开始】
    遍历数组
    for var in ${my_arr[*]}
    do
    echo $var
    done

    2.16文件加载

    在文件中添加 . 脚本名称 或者 source 脚本名称
    #!/bin/bash
    source ./test1.sh # 加载test1.sh 的文件内容

    展开全文
  • Linux中编写Shell脚本

    万次阅读 多人点赞 2018-10-08 11:22:35
    Shell脚本的执行 Shell脚本编写规范 Shell 中的变量 变量的算术运算 双小括号 (()) 数值运算命令的用法 let 运算命令的用法 expr 命令的用法 br 命令的用法 $[]符号的运算示例 Shell脚本的条件测试 几种...
    展开全文
  • 如何编写一个shell脚本

    万次阅读 2010-11-26 19:07:00
    本文结合大量实例阐述如何编写一个shell脚本。 为什么要进行shell编程 在Linux系统中,虽然有各种各样的图形化接口工具,但是sell仍然是一个非常灵活的工具。Shell不仅仅是命令的收集,而且是一门非常棒的编程语言...
  • Shell 脚本中调用另一个 Shell 脚本的三种方式

    万次阅读 多人点赞 2017-07-28 23:38:09
    先来说一下主要以下有几种方式: fork: path/to/foo.sh exec: exec path/to/foo.sh source: source path/to/foo.sh ... foo.sh 这个脚本,比如如果是 foo.sh 在当前目录下,就是 ./foo.sh。运行的时候 terminal 会
  • 执行第一个shell脚本

    千次阅读 2016-07-18 23:17:06
    2、然后使用vim编辑文件,这里会用到这几命令,进入vim编辑器进入编辑模式按键i,然后在文件中编辑shell脚本,俺家esc退出编辑模式进入一般模式,:q命令是不保存退出,:wq是保存退出,其他的命令可以查资料...
  • 最近遇到把sas代码翻译成hive代码中发现这样一个问题,在sas代码中总是很多重复代码:从指标表中读取指标代码,如果指标代码在表中则执行此程序,不在则不执行。sas中有if和goto语句进行判断,但hive中并没有这样的...
  • ubuntu上第一个shell脚本

    万次阅读 2017-05-26 14:54:15
    参考:https://billie66.github.io/TLCL/book/zh/chap25.htmlUbuntu下第一个shell脚本在home目录下新建文件夹xkmkdir xk进入xk文件夹,新建文件helloshelltouch helloshell在helloshell文件中输入如下内容:#!...
  • 一个shell脚本+执行+调试

    千次阅读 2018-06-20 13:58:21
    打开vs code编辑器,新建一个文件test_shell.sh,输入如下代码: #!/bin/bash echo &amp;quot;Hello World !&amp;quot; 执行方法1: chmod +x ./test_shell.sh #使脚本具有执行权限 ./test_shell.sh #...
  • 一个shell脚本启动服务器

    千次阅读 2018-09-17 15:37:43
    使用shell脚本一个一键启动服务器 由于我们服务器使用的docker, nginx和 redis 还有mysql都是docker启动好的 我们项目使用supervisor进程管理工具,需要提前配置,但是不知道什么原因,以前的环境配置出...
  • 脚本调用: 先来说一下主要以下有几种方式: fork: 如果脚本有执行权限的话,path/to/foo.sh。...新开启子shell,需要在父shell定义环境变量的变量子shell才可以使用可以继承环境变量。在脚本中定义环境...
  • shell脚本的作用,第一个shell脚本shell脚本的变量,shell给定的特殊变量,shell脚本的三种赋值方式
  • 一个shell脚本-helloworld

    千次阅读 2015-11-30 22:36:44
    linux shell 只是一个命令解析器,同时支非交互式命令式脚本编程。[^footnote] 因此不同的shell可以用不同的方法解析...第一个shell脚本在此我选择bash脚本作为我的第一个shell脚本,将其命名为helloworld.sh首先要说
  • 一个好玩的Linux shell脚本

    万次阅读 2014-04-28 19:37:39
    一个好玩的Linux shell脚本,基本死调用cowsay
  • 假如有脚本 first.sh: #!/bin/bash #------------------ #@file:first.sh ...如何在其它Shell脚本中调用first.sh呢?主要有三种方法:source、点号以及sh命令。 1.source #!/bin/bash #-------------...
  • 如何写一个shell脚本并运行

    万次阅读 2016-04-10 11:53:32
    1、用shell程序执行脚本:根据你的shell脚本的类型,选择shell程序,常用的有sh,bash,tcsh等(一般来说第行#!/bin/bash里面指明了shell类型的,比如#!/bin/bash指明是bash,#!/bin/sh则是sh);然后输入命令...
  • 现在在用python的scrapy框架写爬虫,爬虫在docker容器里面运行,我们用shell脚本调用docker容器里面的python脚本运行爬虫程序,如何让爬虫程序传一个状态码给shell脚本
  • Shell脚本

    万次阅读 多人点赞 2018-07-30 08:44:01
    20.1 什么是Shell脚本 shell脚本并不能作为正式的编程语言,因为它是在linux的shell中运行的,所以称为shell脚本。事实上,shell脚本就是...我们可以把这10条命令都记录到一个文档中,然后去调用文档中的命令,这样...
  • shell脚本基本语法详解

    万次阅读 多人点赞 2016-09-28 20:16:04
    编写shell脚本的时候,最前面要加上一行:#!/bin/bash,因为linux里面不仅仅只有bash一个解析器,还有其它的,它们之间的语法会有一些不同,所以最好加上这一句话,告诉系统要用这个解析器。一.shell变量shell变量和...
  • 编写一个shell脚本,从键盘读入10个数,显示最大值和最小值。 #! /bin/bash printf &quot;Enter 10 number: &quot; read biggest=$(echo &quot;$REPLY&quot; | tr ' ' '\n' | sort -rn | head...
  • Linux 编写一个简单的Shell脚本

    万次阅读 2017-04-16 21:31:00
    如果我们想让shell 来完成更多的工作,通过把我们的工具一起放置到... 最简单的解释,一个shell 脚本就是一个包含一系列命令的文件。shell 读取这个文件,然后执行文件中的所有命令,就好像这些命令已经直接被输入...
  • 如何写shell脚本?尝试自己编写一个简单脚本。 1 背景介绍 每次发布tomcat项目的时候都要反复敲一些命令,重启tomcat之前先检查tomcat进程有没有停掉,没有还要手动kill该进程,单独部署一个项目还好,如果一次多个...
  • 创建你的第一个Shell脚本

    万次阅读 2015-07-06 16:45:20
    1.写一个脚本 a) 用touch命令创建一个文件:touch my_script b) 用vim编辑器打开my_script文件:vi my_script c) 用vim编辑编辑my_script文件,内容如下: #!/bin/bash 告诉shell使用什么程序解释脚本 #My ...
  • 一个读取文件内容的shell脚本

    万次阅读 2018-01-23 12:13:54
    如下展示的是一个shell脚本,该脚本可以2行2行的读取文件中的内容,然后把读取到的文件内容保存到一个名为target.json的文件当中,然后打印出target.json中的内容,打印完毕删除target.json文件,然后再新建target....

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 562,515
精华内容 225,006
关键字:

如何编辑一个shell脚本