shell脚本简介

shell 脚本简介

摘要:简单介绍 shell 脚本相关的知识,包括变量、字符串、数组、运算、函数、脚本调试等。



一、shell 脚本

shell 是 linux 系统的一个应用程序,通过接受用户输入的命令来启动、暂停、停止程序的运行或对计算机进行控制,从而将用户和 Linux 系统连接起来。

shell 脚本是由 Shell 命令组成的可执行文件,用于完成特定功能。脚本通过解释器运行,速度相对较慢。脚本中最重要的是对 shell 命令的使用与组合以及一些语言特性,来完成想要的功能。

注释:分为单行和多行注释

# sing line commet,单行注释
: << EOF
第一行注释
第二行注释
第 N 行注释
注:多行注释方法多,此法相对安全。
EOF
# 注意 “:” 后面有空格。

二、shell 变量

1. 变量类型

  1. 局部变量:变量在脚本或命令中定义,仅当前 shell 中有效,其他 shell 程序不能访问局部变量。
  2. 环境变量:所有程序都能访问的变量,某些程序的正常运行依赖环境变量。脚本中可定义环境变量。
  3. shell 变量:由 shell 程序设置的特殊变量。shell 变量包括环境变量和局部变量。

2. 变量操作

  1. 普通变量赋值: name=“any text” (“=” 两边无空格)
  2. 局部变量赋值: local name=“any text”
  3. 使用变量: echo $name 或 echo ${name} (推荐使用大括号)
  4. 变量重新赋值: name=“new text”
  5. 只读变量: name=“only_read” -> readonly name
  6. 删除变量: unset name; (不能删除只读变量)

3. 字符串变量

1) 单引号变量: var=‘var string’

   原样输出,单引号中不能有单独的单引号和转义符。

2)双引号:var=“your 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 从第1个开始往后截取4个字符
   name="this is my name";
   echo ${name:1:4} # 输出 is i
   echo ${name::4} # 输出 this

4. 数组

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

1)数组操作

定义数组: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]}

2)参数传递

获取参数值:
    $0 : 固定,代表执行的文件名
    $1 : 代表传入的第1个参数
    $n : 代表传入的第n个参数

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

5. 运算符

  1. 算数运算

    + 、-、*、\ : 乘号前须加\进行转义才可进行乘法运算
    val=`expr 2 + 2` (使用linux命令expr进行辅助运算)
    val=$[2+2] (4个空格不是必要的,不同于条件判断)
    val=$((2+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。
    
  3. 字符串运算符
    下面列出常用的字符串运算符,假设变量 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。
    
  4. 布尔运算符
    下面列出常用的布尔运算符,假定变量 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。
    
  5. 逻辑运算符
    假设变量 a 为 10,变量 b 为 20。

     && :逻辑 AND [[ $a -lt 100 && $b -gt 100 ]] 返回 false
     || :逻辑的 OR [[ $a -lt 100 || $b -gt 100 ]] 返回 true
    
  6. 文件运算符

     -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 系统都支持)
$(ls /etc) : $+() (部分unix系统不支持)

说明:多个嵌套使用时,从内向外执行 for file in \s /etc\ 或 for file in $(ls /etc) 循环中使用。

获取脚本文件所在的目录

dirname \$0` 获取脚本文件所在的目录。
path=$(cd `dirname $0`;pwd) : 获取脚本当前所在目录,并且执行cd命令到达该目录,使用pwd获取路径并赋值到path变量

2. 运算和判断

算术运算:

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

逻辑判断:

[ ] : 中括号旁边和运算符两边必须添加空格 (可以使用,不推荐)
[[ ]]:中括号旁边和运算符两边必须添加空格 (字符串验证时,推荐使用)
(()) : 中括号旁边和运算符两边必须添加空格 (数字验证时,推荐使用)
[[]] 和 (()) 分别是[ ]的针对数学比较表达式和字符串表达式的加强版。

注:使用[[ … ]]条件判断结构,而不是[ … ],能够防止脚本中的许多逻辑错误。例如,&&、||、<和> 操作符能够正常存在于[[ ]]条件判断结构中,但是如果出现在[ ]结构中的话,会报错。如可以直接使用 if [[ $a != 1 && $a != 2 ]], 如果不适用双括号, 则为if [ $a -ne 1] && [ $a != 2 ] 或者 if [ $a -ne 1 -a $a != 2 ]。

[[ ]] 中增加模式匹配特效;

(( )) 不需要再将表达式里面的大小于符号转义,除可使用标准的数学运算符外,还增加以下符号:

符号描 述
val++后增
val--后减
++val先增
--val先减
!逻辑求反
~位求反
**幂运算
<<左位移
>>右位移
&位布尔和
|位布尔或
&&逻辑和
||逻辑或

3. 输出

echo : 仅用于字符串的输出,没有 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个字符(- 表示左对齐,无则右对齐),若不足则以空格填充,超过也会全部显示内容。
  • %-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位的八进制值字符

4. 流程控制

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

if else

ifif condition then command1 command2 ... commandN fi
if elseif condition then command1 command2 ... commandN else command fi
if else-if elseif 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

跳出循环

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

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

[ function ] funname [()]
{
    action;
    [return int;]
}
  • 函数定义 [ 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.

输入重定向

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

输出重定向

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

读取外部输入

命令:`read arg` (脚本读取外部输入并赋值到变量上)

停止执行当前脚本,等待外部输入,并将输入赋值到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";

输出结果:

SUCCESS: yay
WARNING: hmm
ERROR: fubar

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

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

四、脚本运行举例

1. shell 操作 mysql

下面以登录 mysql 作为例子,来看如何运用 shell 脚本来操作数据库,并导入数据。

/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。

2. shell 脚本调试

检查是否有语法错误-n:

bash -n script_name.sh

使用下面的命令来执行调试 Shell 脚本-x:

bash -x script_name.sh

调试 count_odd_number.sh 程序:

    #!/usr/bin.env bash
    # 用于计算数组中奇数的和
    # @author San Zhang 
    # @time 2022/02/11
    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. 调试结果如下:
    1. + sum=0
    2. + for num in 1 2 3 4
    3. + re=1%2
    4. + (( 1%2 == 1 ))
    5. + sum=1
    6. + for num in 1 2 3 4
    7. + re=2%2
    8. + (( 2%2 == 1 ))
    9. + for num in 1 2 3 4
    10. + re=3%2
    11. + (( 3%2 == 1 ))
    12. + sum=4
    13. + for num in 1 2 3 4
    14. + re=4%2
    15. + (( 4%2 == 1 ))
    16. + echo 4
    17. 4
    上面显示了程序执行的每一步,通过观察程序执行的步骤是否满足预期从而达到调试的效果。
    带有 + 表示的是 Shell 调试器的输出,不带 + 表示程序的输出。

3. 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 $?

五、参考资料

  1. Shell 教程,菜鸟教程
  2. 一篇教会你写90%的shell脚本,华清远见

六、注意事项

上文中部分文字有改动,以便阅读。代码上未作检查,若有问题,还需调试或自行查找。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值