shell脚本入门

shell

这里说的shell表示shell脚本。

shell是和php、python这类一样的解释性语言。在Linux中shell的种类有:

  • Bourne Shell(/usr/bin/sh或/bin/sh)
  • Bourne Again Shell(/bin/bash)
  • C Shell(/usr/bin/csh)
  • K Shell(/usr/bin/ksh)
  • Shell for Root(/sbin/sh)
  • ……

通常默认的是bash,所以之后都是在bash下执行的。

第一个shell脚本

创建一个hello.sh,然后编辑一下内容,

#!/bin/bash
echo "Hello World!!"

这个程序只有两行,第一行告诉系统解释此脚本的shell程序;第二行是打印内容。

执行这个程序有两种方法:

作为可执行程序

chmod +x hello.sh
./hello.sh

首先给程序赋予执行权限,然后执行脚本。

在Linux中执行二进制程序需要./hello.sh,这是告诉系统在当前目录中寻找并执行该程序;如果使用hello.sh,系统会在PATH中寻找程序。

作为解释器参数

/bin/bash hello.sh

直接运行解释器。这样就不用在程序中指定解释器了。

注释

shell中的注释只有#,进行单行注释。

如果想要多行注释有两种方法:

  • 将内容定义为一个函数,且不进行调用;

  • 或者

    :<<EOF
    xxxxx
    yyyyy
    zzzzz
    EOF
    

输出

echo

shell中的echo用于字符串输出,默认换行。

printf

printf模仿C程序中的printf程序,移植性比echo会更好。

printf可以格式化输出,不过不会自动换行。

一些实例

printf "Hello "
printf "World!\n"    # 不会自动换行
printf "%10s %8s %4.2f\n" WWW female 46.35426    # 格式化输出
printf '%d %s\n' 123 "dcba"    # 单引号和双引号效果一样
printf "%s" aaa bbb ccc    # 多出的参数同样会输出
printf '\n'
printf "%s\n" ddd eee fff    # 多出的参数会格式输出
printf "%s and %d and %f"    # 如果不指定会有默认值

# output: 
# Hello World!
#        WWW   female 46.35
# 1234 dcba
# aaabbbccc
# ddd
# eee
# fff
#  and 0 and 0.000000

格式化字符串有:

  • %d --- 十进制整数
  • %s --- 字符串
  • %c --- 字符
  • %f --- 浮点
  • %e --- 科学计数法输出
  • %o --- 无符号八进制
  • %x --- 无符号十六进制(形式为fff
  • %X --- 无符号十六进制(形式为FFF
  • %#X --- 无符号十六进制(形式为0xFFF

对应的位置必须是相应的格式,否则会报错。格式化输出可以进行修饰:

  • - :对齐方式;默认右对齐,有-左对齐;
  • 0:补零;
  • m.nm表示长度,包括整数和小数部分;n值精度,即小数位数;

具体的可以参考:printf格式化字符输出对应解释 - biblog - 博客园 (cnblogs.com)

变量

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

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

shell中定义变量

var=123
str="hello"

一定要注意变量名和等号之间不能有空格。同时变量名只能使用数字字母下划线,首个字符不能以数字开头。

使用变量时需要加上$符号,为了区分变量的边界最好加上{}花括号,

var=123
str="Hello"
echo "> The number is ${var}"
echo "> Say ${str}"

# output:
# > The number is 123
# > Say Hello
#

如果要删除一个变量可以使用unset

unset var

变量被删除后不能再次使用。

在shell中还有一种只读变量,使用readonly定义,

x=123
readonly x

只读变量被定义后不能被修改。

unset不能删除只读变量。


2022年3月7日-补充

在文件夹下有文件a.sh,里面内容为:

#!/bin/bash
var="*.sh"
echo $var
echo "$var"
echo ${var}

运行结果是什么?

a.sh
*.sh
a.sh

发现$var${var}都被当成命令执行了,所以变量使用时最好加上双引号。⚠️

字符串

字符串定义可以使用单引号''或者双引号"",两者的区别是单引号中的任何内容会原样输出

var=123
echo '> number is ${var}'
echo "> number is ${var}"

# output: 
# > number is ${var}
# > number is 123
# 

字符串拼接

拼接字符串可直接s1s2s3这样,

s1="AAA"
s2="BBB"
echo ${s1}${s2}
echo ${s1}   ${s2}
echo ${s1}"   "${s2}
echo ${s1}"---"${s2}

# output: 
# AAABBB
# AAA BBB
# AAA   BBB
# AAA---BBB
# 

需要注意第4行的输出,中间有多个空格时,只输出了一个。

字符串操作很多,另起文章说明,这里就不过多介绍了。

数组

bash支持一维数组,并且没有限定数组的大小。bash中的数组与C语言的类似,下标由0开始编号,获取数组的元素要利用下标,下标可以是整数或者算术表达式,其值应该大于等于0.

定义一个数组

array=(12 324 1234)
echo "array: ${array[@]}"    # 输出所有元素
echo "array[0]: ${array[0]}"    # 输出第一个元素
echo "array length: ${#array[@]}"    # 输出元素个数
echo "array[0] length: ${#array[0]}"    # 输出第一个元素长度

# output:
# array: 12 324 1234
# array[0]: 12
# array length: 3
# array[0] length: 2
# 

参数

shell脚本中可以使用$n来获取参数,n为数字,代表第几个参数。

echo "File name: ${0}"
echo "First parameter: ${1}"
echo "Second parameter: ${2}"
echo "The number of parameters: ${#}"
echo "The all parameters: ${*}"

当运行的时候指定参数,

# ./ch1.sh www yyy
File name: ./ch1.sh
First parameter: www
Second parameter: yyy
The number of parameters: 2
The all parameters: www yyy

一些特殊字符用来处理参数:

参数处理 说明
$# 参数个数
$* 所有参数
$@ 所有参数
$! 后台运行的最有一个进程的ID
`$ 参数处理
-------- -------------------------------------------------------
 | 脚本运行时的当前进程ID                                  |

| $- | 显示Shell使用的当前选项,与set命令相同 |
| $? | 显示最后命令的退出状态。0表示正常,其他任何值都表示错误 |

$*$@的区别

$*$@都是引用所有参数,但是在双引号中,$*将参数当成一个整体,而$@会将参数分开。

echo "-- \$* ---"
for i in "$*"; do
    echo $i
done

echo "-- \$@ ---"
for i in "$@"; do
    echo $i
done

执行脚本,

# ./ch1.sh www yyy
-- $* --
www yyy
-- $@ --
www
yyy

其他的说明

如果传递的参数包含空格,应该使用单引号或双引号将参数括起来,以便于脚本将这个参数作为整体接受。

运算符

算数运算符

原生bash不支持简单的数学运算,需要通过其他命令实现,最常用的是expr

res = `expr 2 + 2`
echo "2 + 2 = ${res}"

需要注意两点:

  • 表达式和运算符之间要有空格
  • 完整的表达式要用反引号括住

补充:

  1. 相加的另一种方法:$(($a+$b))
  2. 还有一种方法:$[a+b]
  3. 看到有人推荐使用 $(expr 2 + 2)

常用的算数运算符及其使用:

运算符 说明 举例
+ 加法 `expr $a + $b`
- 减法 `expr $a - $b`
* 乘法 `expr $a \* $b`
/ 除法 `expr $a / $b`
% 取余 `expr $a \% $b`
= 赋值 a=$b
== 相等 [ $a == $b ]
!= 不相等 [ $a != $b ]

需要注意的是乘法,表达式中需要转义。注意这里需要有空格的,但是怎么都显示不了。。。(不知道cnblogs怎么回事)

a=10
b=20

val=`expr $a + $b`
echo "a + b : $val"

val=`expr $a - $b`
echo "a - b : $val"

val=`expr $a \* $b`
echo "a * b : $val"

val=`expr $b / $a`
echo "b / a : $val"

val=`expr $b % $a`
echo "b % a : $val"

# output:
# a + b : 30
# a - b : -10
# a * b : 200
# b / a : 2
# b % a : 0
# 

关系运算符

关系运算符只支持数字,不支持字符串,除非字符串的值是数字。

运算符 说明 举例
-eq == [ $a -eq $b ]
-ne != [ $a -ne $b ]
-gt > [ $a -gt $b ]
-lt < [ $a -lt $b ]
-ge >= [ $a -ge $b ]
-le <= [ $a -le $b ]

运行实例

a=10
b=20

if [ $a -eq $b ]
then
   echo "$a -eq $b : a 等于 b"
else
   echo "$a -eq $b: a 不等于 b"
fi
if [ $a -ne $b ]
then
   echo "$a -ne $b: a 不等于 b"
else
   echo "$a -ne $b : a 等于 b"
fi
if [ $a -gt $b ]
then
   echo "$a -gt $b: a 大于 b"
else
   echo "$a -gt $b: a 不大于 b"
fi
if [ $a -lt $b ]
then
   echo "$a -lt $b: a 小于 b"
else
   echo "$a -lt $b: a 不小于 b"
fi
if [ $a -ge $b ]
then
   echo "$a -ge $b: a 大于或等于 b"
else
   echo "$a -ge $b: a 小于 b"
fi
if [ $a -le $b ]
then
   echo "$a -le $b: a 小于或等于 b"
else
   echo "$a -le $b: a 大于 b"
fi

# output:
# 10 -eq 20: a 不等于 b
# 10 -ne 20: a 不等于 b
# 10 -gt 20: a 不大于 b
# 10 -lt 20: a 小于 b
# 10 -ge 20: a 小于 b
# 10 -le 20: a 小于或等于 b
# 

布尔运算符

常用的布尔运算符

运算符 说明 举例
! 非运算 [ ! false ]
-o 或运算 [ true -o false ]
-a 与运算 [ true -a false ]

一些实例

a=10
b=20

if [ $a != $b ]
then
   echo "$a != $b : a 不等于 b"
else
   echo "$a == $b: a 等于 b"
fi
if [ $a -lt 100 -a $b -gt 15 ]
then
   echo "$a 小于 100 且 $b 大于 15 : 返回 true"
else
   echo "$a 小于 100 且 $b 大于 15 : 返回 false"
fi
if [ $a -lt 100 -o $b -gt 100 ]
then
   echo "$a 小于 100 或 $b 大于 100 : 返回 true"
else
   echo "$a 小于 100 或 $b 大于 100 : 返回 false"
fi
if [ $a -lt 5 -o $b -gt 100 ]
then
   echo "$a 小于 5 或 $b 大于 100 : 返回 true"
else
   echo "$a 小于 5 或 $b 大于 100 : 返回 false"
fi

# output:
# 10 != 20 : a 不等于 b
# 10 小于 100 且 20 大于 15 : 返回 true
# 10 小于 100 或 20 大于 100 : 返回 true
# 10 小于 5 或 20 大于 100 : 返回 false
# 

逻辑运算符

逻辑运算符

运算符 说明 举例
&& 逻辑与AND [[ $a -lt 100 && $b -gt 100 ]]
|| 逻辑或OR [[ $a -eq 100 || $b -ge 100 ]]

一些实例

a=10
b=20

if [[ $a -lt 100 && $b -gt 100 ]]
then
   echo "返回 true"
else
   echo "返回 false"
fi

if [[ $a -lt 100 || $b -gt 100 ]]
then
   echo "返回 true"
else
   echo "返回 false"
fi

# output:
# 返回 false
# 返回 true
# 

这里使用了[[]],与[]是有一些差别的:

  • []表达式中,常见的><需要加转义字符,表示字符串大小比较,以ASCII码位置比较。不能直接使用><,还有||&&,需要使用-a-o
  • [[]]运算符是[]的扩展,支持<>而不用转义,还是以字符串大小比较。同时支持||&&,不再使用-a-o

字符串运算符

字符串运算符

运算符 说明 举例
= 检查字符串是否相等 [ $a = $b ]
!= 检查字符串是否不等 [ $a != $b ]
-z 检查字符串长度是否为0 [ -z $a ]
-n 检查字符串长度是否不为0 [-n "$a" ]
` 运算符 说明
------ ----------------------- --------------
\| 检查字符串是否为不为空  \| `[ $a]`          |

一些实例

a="abc"
b="efg"

if [ $a = $b ]
then
   echo "$a = $b : a 等于 b"
else
   echo "$a = $b: a 不等于 b"
fi
if [ $a != $b ]
then
   echo "$a != $b : a 不等于 b"
else
   echo "$a != $b: a 等于 b"
fi
if [ -z $a ]
then
   echo "-z $a : 字符串长度为 0"
else
   echo "-z $a : 字符串长度不为 0"
fi
if [ -n "$a" ]
then
   echo "-n $a : 字符串长度不为 0"
else
   echo "-n $a : 字符串长度为 0"
fi
if [ $a ]
then
   echo "$a : 字符串不为空"
else
   echo "$a : 字符串为空"
fi

# output:
# abc = efg: a 不等于 b
# abc != efg : a 不等于 b
# -z abc : 字符串长度不为 0
# -n abc : 字符串长度不为 0
# abc : 字符串不为空
# 

特别说明

[-n "$a" ],这里为什么要加双引号?

a=""
if [ -n $a ]
then
   echo "-n $a : 字符串长度不为 0"
else
   echo "-n $a : 字符串长度为 0"
fi

输出结果为:

-n  : 字符串长度不为 0

从结果上看 -n $a 返回 true,这并正确,正确的做法是 $a 这里应该加上双引号,否则 -n $a 的结果永远是 true:

a=""
if [ -n "$a" ]
then
   echo "-n $a : 字符串长度不为 0"
else
   echo "-n $a : 字符串长度为 0"
fi

输出结果为:

-n  : 字符串长度为 0

文件测试运算符

文件测试运算符用于检测Unix文件的各种属性。

操作符 说明 举例
-b 是否是块文件 [ -b $file ]
-c 是否是字符设备文件 [ -c $file ]
-d 是否是目录 [ -d $file ]
-f 是否是普通文件 [ -f $file ]
-g 是否设置了SGID位 [ -g $file ]
-k 是否设置了粘着位(Sticky Bit) [ -k $file ]
-p 是否是有名管道 [ -p $file ]
-u 是否设置了SUID位 [ -u $file ]
-r 是否可读 [ -r $file ]
-w 是否可写 [ -w $file ]
-x 是否可执行 [ -x $file ]
-s 是否为空 [ -s $file ]
-e 是否存在 [ -e $file ]
-L 是否是一个符号链接 [ -L $file ]

一些实例

file="/var/www/runoob/test.sh"
if [ -r $file ]
then
   echo "文件可读"
else
   echo "文件不可读"
fi
if [ -w $file ]
then
   echo "文件可写"
else
   echo "文件不可写"
fi
if [ -x $file ]
then
   echo "文件可执行"
else
   echo "文件不可执行"
fi
if [ -f $file ]
then
   echo "文件为普通文件"
else
   echo "文件为特殊文件"
fi
if [ -d $file ]
then
   echo "文件是个目录"
else
   echo "文件不是个目录"
fi
if [ -s $file ]
then
   echo "文件不为空"
else
   echo "文件为空"
fi
if [ -e $file ]
then
   echo "文件存在"
else
   echo "文件不存在"
fi

# output:
# 文件可读
# 文件可写
# 文件可执行
# 文件为普通文件
# 文件不是个目录
# 文件不为空
# 文件存在
# 

test命令

test命令用于检查某个条件是否成立,可以进行数值、字符和文件三个方面的测试。

不过直接用[]或者[[]]也行。

几个实例

num1=100
num2=100
if test $[num1] -eq $[num2]
then
    echo '两个数相等!'
else
    echo '两个数不相等!'
fi

# output:
# 两个数相等!
# 
#--------------------------

num1="ru1noob"
num2="runoob"
if test $num1 = $num2
then
    echo '两个字符串相等!'
else
    echo '两个字符串不相等!'
fi

# output:
# 两个字符串不相等!
# 
#--------------------------

cd /bin
if test -e ./notFile -o -e ./bash
then
    echo '至少有一个文件存在!'
else
    echo '两个文件都不存在'
fi

# output:
# 至少有一个文件存在!
# 

流程控制

条件

# 用法1
if condition
then
    ...
fi

#--------------------
# 用法2
if condition;then
    ...
fi

#--------------------
# 用法3
if condition
then
    ...
else
    ...
fi

#--------------------
# 用法4
if condition
then
    ...
elif condition
then
    ...
else
    ...
fi

case

case 值 in
    模式1)
        ...
        ;;
    模式2)
        ...
        ;; 
     *)
        ...
        ;;
esac

循环

for循环

for var in item1 item2 item3
do
    ...
done

while循环

while condition
do
    ...
done

until循环

很少用。

until condition
do
    ...
done

函数

函数的定义方式为:

[function] funname[()]{
    ...
    [return]
}

[]中的内容表示可以省略。

实例

function demo1(){
    echo "这是一个函数"
}

demo1

# output:
# 这是一个函数
# 

如果有参数和返回值,

function demo2(){
    a=$1
    b=$2
    echo "第一个参数:$1"
    echo "第二个参数:$2"
    echo "a + b: $(expr $a + $b)"
}

demo2 2 3
echo $?

# output
# 第一个参数:2
# 第二个参数:3
# a + b: 5
# 0
# 

这里的$?输出为0,在shell中0表示正常,除此之外的任何值都是有问题。函数中最后执行的是echo,是不会有返回值的,所以执行正常返回为0,准确的说并不是返回值,而是最后一条指令的退出状态为0

如果修改为

function demo2(){
    a=$1
    b=$2
    echo "第一个参数:$1"
    echo "第二个参数:$2"
    echo "a + b: $(expr $a + $b)"
    return $(expr $a + $b)
}

demo2 2 3
echo $?

# output
# 第一个参数:2
# 第二个参数:3
# a + b: 5
# 5
# 

这样最后的指令为求和,返回的就是结果了。

输入输出重定向

大多数 UNIX 系统命令从终端接受输入并将所产生的输出发送回到终端。一个命令通常从标准输入读取输入,默认情况下,这就是终端。同样,通常将输出写入到标准输出,默认情况下,这也是终端。

重定向列表:

命令 说明
command > file 输出重定向到file
command < file 输入重定向到file
command >> file 以追加的方式重定向到file
n > file 将文件描述符为n的文件重定向到file
n >> file 将文件描述符为n的文件以追加的方式重定向到file
n >& m 将输出文件m和n合并
n <& m 将输入文件m和n合并
<< tag 将开始标记tag和结束标记tag之间的内容作为输入

关于文件描述符

  • 0表示标准输入STDIN
  • 1表示标准输出STDOUT
  • 2表示标准错误输出STDERR

一些实例

$ wc -l users
$ wc -l < users

这两个命令结果是不同的,第一个会输出文件名,第二个不会,因为命令仅知道是从标准输入读取内容的。

$ command < infile > outfile

命令将输入重定向到infile,将输出重定向到outfile。

Here Document

没有具体翻译,是一种文本的格式,形式为

command << delimiter
    document
delimiter

说明:

  • delimiter可以是任何的符号;
  • 结尾的delimiter一定要顶格。

一些实例

$ wc -l << EOF
   000
   111
   222
EOF

/dev/null

/dev/null是一个特殊的文件,写入到这里的内容都会被丢弃;如果尝试读取文件内容,那么什么都读不到。

如果不希望屏幕上显示输出结果,可以将输出重定向到/dev/null

$ command > /dev/null

如果不希望显示错误信息,可以这样,

$ command 2>/dev/null

其他的说明

$ command > file 2>&1
$ command >> file 2>&1

这里&没有固定的意思。放在>后面的&,表示重定向的目标不是一个文件,而是一个文件描述符;换言之,2>1表示将stderr重定向到当前路径下文件名为1regular_file中;而2>&1代表将stderr重定向到文件描述符为1的文件,即/dev/stdout中。

>&file&>file的意思是完全相同的。

文件包含

shell可以包含外部脚本。语法格式为

. filename
source filename

被包含的文件不需要可执行权限。

posted @ 2022-03-08 21:37  pwolp  阅读(148)  评论(0)    收藏  举报