初学者编写bash脚本教程

bash shell script 定义

bash

bash是命令语言解释器。广泛用于各种gun/unix系统上的默认命令解释器。全程叫做“Bourne-Again SHell”

shell

shell是一个宏处理器,允许执行交互式或非交互式的命令。

scripting

脚本允许自动执行,否则会一个接一个命令交互执行。

什么是shell

shell允许你通过命令与计算机交互,从而检索或存储数据、处理信息和其它各种简单甚至非常复杂的任务。

所以:你每天都会执行date cal pwd ls

什么是scripting

script就是把上边的4个命令放在一个文本里,然后一起执行。

bash是什么

bash是许多GNU/Linux系统上的默认解释器

查看默认解释器执行命令:echo $SHELL

[root@localhost ~]# echo $SHELL
/bin/bash

#! shebang,定义脚本的shell解释器,写在脚本的第一行如:

#!/bin/bash

通过which bash查找bash可执行二进制文件的完整路径。

文件名和权限

要执行脚本,就要给脚本加执行的权限:chmod +x filename

通过file 命令查看文件的类型 file filename

[root@localhost ~]# file a.sh
a.sh: Bourne-Again shell script, ASCII text executable
[root@localhost ~]# file epel-7.repo
epel-7.repo: ASCII text

脚本执行

1、指定解释器执行脚本

[root@localhost ~]# /bin/bash date.sh
Sat Jul 14 09:01:39 UTC 2018

2、脚本文件头定义shebang,文件加执行权限

[root@localhost ~]# cat date.sh
#!/bin/bashdate
[root@localhost ~]# chmod +x date.sh
[root@localhost ~]# ./date.sh
Sat Jul 14 09:02:41 UTC 2018

相对路径和绝对路径

绝对路径:

在linux中绝对路径就是从根/开始,如:/usr/local/httpd

相对路径:

就是路径开头没有根/,如:cd abc/test/

路径切换

cd 不加任何参数,切换到当前用户home目录。

cd ~ 都是切换到当前用户home目录。

cd 加路径 切换到目标路径,如:cd /usr/local

hello world 脚本

[root@localhost ~]# echo '#!/bin/bash' > hello.sh
[root@localhost ~]# echo 'echo "hello world"' >> hello.sh
[root@localhost ~]# cat hello.sh
#!/bin/bash
echo "hello world"
[root@localhost ~]# chmod +x hello.sh
[root@localhost ~]# ./hello.sh
hello world

简单备份bash shell脚本

vi backup.sh    #创建脚本
# !/bin/bash
tar czf /tmp/root.tar /root/abcchmod +x backup.sh  #加执行权限./backup.sh #执行脚本[root@localhost]# ls /tmp/root.tar  #验证执行结果
/tmp/root.tar

常用变量

给变量赋值的不同方式

双引号"" 允许用过$符号引用其他变量值

单引号'' 禁止引用其他变量值,$视为普通字符

反撇号`` 将命令执行的记过输出给变量;如:

sed -i s/"B"/"b"/g `grep -rl "B" --exclude="*.sql" ceshi/*`  将grep得到的文件传输给sed使用

用户自定义变量

设置变量的作用范围

格式:

export 变量名

export 变量名=变量值

清除变量名:

unset 变量名

自定义变量
[root@ceshi ~]# export a            #自定义变量a
[root@ceshi ~]# export b=222        #自定义变量b
[root@ceshi ~]# a=111               #给变量a赋值[root@ceshi ~]# echo $a             #打印变量a的值
111
[root@ceshi ~]# echo $b             #打印变量b的值
222清除变量:
[root@ceshi ~]# unset a             #清除变量a
[root@ceshi ~]# unset b             #清除变量b
[root@ceshi ~]# echo $a             #输出为空[root@ceshi ~]# echo $b

环境变量

环境变量配置文件

  • 全局配置文件:/etc/profile

  • 用户配置文件:~/.bash_profile

查看环境变量:

set 命令可以查看所有的shell变量,其中包括环境变量

常见的环境变量

  • $USER 查看当前用户
  • $logname 登录相关信息
  • $UID 当前用户的UID,root用户为0
  • $SHELL 打印当前用户的shell编辑器
  • $HOME 打印当前用户的主目录
  • $pwd 打印当前所在目录
  • $PATH 用户输入的命令在哪里目录中查找
  • $PS1
  • $PS2
  • $RANDOM 显示一个随机数

位置变量

表示为:$n (n为1-9之间的数字)

#./test.sh one two three four five six
  • $0 表示脚本文件名本身

  • $1 表示one

  • $2 表示two

    依次类推

    $n 这个程式的第n个参数值,n=1..9

预定义变量

  1. $# 命令行中位置参数的个数
  2. $* 所有位置参数的内容
  3. $? 上一条命令执行后返回的状态,为0表示成功,为非0表示执行异常或出错
  4. $$ 当前所在进程的进程号
  5. $! 上一个指令PID 后台运行的最后一个进程号
  6. $0 当前执行的进程/程序名
  7. $- 显示shell使用的当前选项,与set命令功能相同
  8. $@ 跟$*类似,但是可以当作数组用

变量

变量是编程的本质。变量允许程序员在整个脚本中存储、修改和重用数据。

创建一个新脚本welcome.sh:

#!/bin/bashgreeting="Welcome"
user=$(whoami)
day=$(date +%A)echo "$greeting back $user! Today is $day."
echo "Your bash shell version is:$BASH_VERSION."

解释脚本:

第一部分:

shebang 指定shell解释器

greeting:定义一个变量,并赋值一个字符串值。

user:定义一个变量,赋值是通过一个命令替换技术来完成的。通过运行whoami获取当前的用户名,然后赋值给user。

day:定义一个变量,赋值同上;+%A代表只显示周几。

第二部分:

使用echo向终端打印信息,$greeting用来调用变量对应的值Welcome;$user同样用来调用变量对应的用户名;date也一样。

注意:不要使用大写命名私有变量,因为大写变量名都是内置变量。如果覆盖内置变量,可能导致功能失调。

终端使用变量

[root@localhost ~]# a=5 #定义变量
[root@localhost ~]# b=6
[root@localhost ~]# echo $a #调用变量
5
[root@localhost ~]# echo $b
6
[root@localhost ~]# echo $[$a+$b] #变量算数
11

利用变量更新备份脚本,生成更有意义的文件名:

vi backup.sh

#!/bin/bash
# this bash script is backup a general user's home directory to /tmp/user=$(whoami)
input=/home/$user
output=/tmp/${user}_home$(date +%Y-%m-%d_%H%M%S).tar.gztar czf $output $input
echo "Backup of $input completed!! Details about the output backup file:"
ls -l $output

脚本第五行:${parameter}叫参数展开,将花括号中的内容挨个和外面的内容结合。

[abc@localhost ~]$ ./backup.sh
tar: Removing leading `/' from member names
Backup of /home/abc completed!! Details about the output backup file:
-rw-rw-r-- 1 abc abc 741 Jul 14 13:59 /tmp/abc_home2018-07-14_135933.tar.gz

输入、输出、错误重定向

名词

通常在GNU/Linux命令行上执行的命令要么产生输出,要么要求输入,要么抛出错误消息。这是shell脚本的基本概念,也是使用GNU/Linux命令行的基本概念。

每次执行命令时,可能会发生三种可能的结果。第一个场景是命令将产生一个预期的输出,其次,该命令将生成一个错误,最后,您的命令可能根本不会产生任何输出。

标准输出如下:

终端输出

[abc@localhost ~]$ ls backup.sh
backup.sh

重定向输出

重定向标识符:“>”

1、">" 输出重定向

重定向会将 标准输出 重定向到指定目标。

[abc@localhost ~]$ touch foobar
[abc@localhost ~]$ ls foobar barfoo
ls: cannot access barfoo: No such file or directory
foobar
[abc@localhost ~]$ ls foobar barfoo > stdout.txt
ls: cannot access barfoo: No such file or directory
[abc@localhost ~]$ cat stdout.txt
foobar

2、"2>" 错误输出重定向

重定向将 标准错误 重定向到指定目标。

[abc@localhost ~]$ ls foobar barfoo 2> stderr.txt
foobar
[abc@localhost ~]$ cat stderr.txt
ls: cannot access barfoo: No such file or directory

3、"&>" 输出和错误 重定向 输出

重定向将 标准输出和标准错误 一块重定向到指定目标。

[abc@localhost ~]$ ls foobar barfoo &> stdoutandstderr.txt
[abc@localhost ~]$ cat stdoutandstderr.txt
ls: cannot access barfoo: No such file or directory
foobar

4、“>>” 追加重定向输出

追加重定向输出,不覆盖文件,直接追加到文件结尾。

标准错误:

1、终端输出

[abc@localhost ~]$ ls test.sh
ls: cannot access test.sh: No such file or directory

2、"2>" 错误输出 重定向

重定向将 标准错误 重定向到指定目标。

[abc@localhost ~]$ ls foobar barfoo 2> stderr.txt
foobar
[abc@localhost ~]$ cat stderr.txt
ls: cannot access barfoo: No such file or directory

标准输入

终端输入

通常终端输入来自键盘,所以输入的任何内容都称为stdin(标准输入)。

文件输入

接受来自文件的命令输入。使用“<”符号接受文件输入。

我们先用cat 命令,通过标准输入然后,重定向到file.txt:

[abc@localhost ~]$ cat > file.txt
hello world.
welcome backup.
[abc@localhost ~]$ cat file.txt
hello world.
welcome backup.

我们再用cat命令,通过< 符号输入file.txt的内容:

[abc@localhost ~]$ cat < file.txt
hello world.
welcome backup.

重定向输入

1、“<” 输入重定向

[root@localhost log]# cat >aaa.txt<<EOF
> hello world
> EOF
[root@localhost log]# cat aaa.txt
hello world

2、“<<” 追加输入重定向

[root@localhost log]# cat >>aaa.txt<<EOF
> 123456789
> HELLO WORLD
> EOF
[root@localhost log]# cat aaa.txt
hello world
123456789
HELLO WORLD

read输入

从键盘或文件中获取标准输入:read命令

语法:read 变量名

[abc@localhost ~]$ read a
abc
[abc@localhost ~]$ echo $a
abc
[abc@localhost ~]$ echo "the word you entered is: $a"
the word you entered is: abc
[abc@localhost ~]$ read a1 a2 #同时输入两个字段
aaa bbb
[abc@localhost ~]$ echo $a1
aaa
[abc@localhost ~]$ echo $a2
bbb

详细见:https://blog.51cto.com/506554897/2114407

backup.sh优化

之前的backup脚本执行时有个错误:

tar: Removing leading "/" from member names

消息告诉我们,绝对路径已被删除,从而提取了压缩文件,而不是覆盖任何现有文件。并没有造成任何影响。

因此我可以利用重定向消除这种不必要的信息。通过重定向stderr到/dev/null,/dev/null作为数据接收器,它丢弃任何重定向到它的数据,可以用man null查看详细。

我们将脚本修改如下:

[abc@localhost ~]$ vi backup.sh
#!/bin/bash
# this bash script is backup a general user's home directory to /tmp/user=$(whoami)
input=/home/$user
output=/tmp/${user}_home$(date +%Y-%m-%d_%H%M%S).tar.gztar czf $output $input 2> /dev/null
echo "Backup of $input completed!! Details about the output backup file:"
ls -l $output
[abc@localhost ~]$ ./backup.sh
Backup of /home/abc completed!! Details about the output backup file:
-rw-rw-r-- 1 abc abc 893 Jul 14 14:59 /tmp/abc_home2018-07-14_145919.tar.gz

执行后没有了报错信息。

函数

##名词

函数允许程序员组织和重用代码,从而提高了整个脚本的效率、执行速度和可读性。

当注意到脚本包含两行相同的代码时,可以考虑使用一个函数。

函数是将不同命令的组号组合成当个命令的方法。如果所需的输出或计算由多个命令组成,并且在整个脚本执行过程中需要多次,这可能非常有用。函数是使用函数关键字定义,后面是用花括号括起来的函数体。如:user_details {}

定义函数方法:

function 函数名{函数体(命令)
}
函数名() {函数体(命令)
}
函数名()
{函数体(命令)
}

看个例子:

[abc@localhost ~]$ vi function.sh
#!/bin/bashfunction user_details {echo "User Name: $(whoami)"echo "User Home: $HOME"
}user_details

详细描述:

function是函数关键字,user_details是函数名。

花括号里边的是函数体,包括两个echo命令,在两个echo之前有缩进,缩进为1个tab,4个空格。缩进可以择自己的缩进方式。

最后的user_details 是在调用函数。

注意:函数定义必须在函数调用之前进行,否则脚本将返回stderr:command not found错误。

优化backup.sh

#!/bin/bash# this bash script is backup a general user's home directory to /tmp/user=$(whoami)
input=/home/$user
output=/tmp/${user}_home$(date +%Y-%m-%d_%H%M%S).tar.gzfunction total_files {find $1 -type f | wc -l
}function total_directory {find $1 -type d | wc -l
}tar czf $output $input 2> /dev/nullecho -n "files to be included:"
total_files $inputecho -n "directory to be included:"
total_directory $inputecho "Backup of $input completed!!" echo "Details about the output backup file:"
ls -l $output

代码片段:

定义tottal_files函数,功能利用function find() { [native code] }和wc命令,来统计 提供给函数调用的目录的文件数量。

定义total_diretory函数,功能利用function find() { [native code] }和wc命令,来统计 提供给函数调用的目录的目录数量。

执行脚本显示如下:

数值和字符串比较

数字和字符串的基本比较运算符

描述 数值比较 字符串比较
少于 less than -lt <
大于 greater than -gt >
等于 equal -eq =
不等于 not equal -ne !=
小于或等于 -le N/A
大于或等于 -ge N/A
字符串不是空 N/A -n
字符串是空 N/A -z

N/A:代表不可用/不适用

-n $1:字符串$1不是空的

-z $1:字符串$1 为空

当两个数值比较后,可以用echo $?来检查返回值,来查看结果是True 返回0;结果是False返回为1。

数值比较

[abc@localhost ~]$ a=1
[abc@localhost ~]$ b=2
[abc@localhost ~]$ [ $a -lt $b ]
[abc@localhost ~]$ echo $?  #正确返回0
0
[abc@localhost ~]$ [ $a -gt $b ]
[abc@localhost ~]$ echo $?  #不正确返回1
1
[abc@localhost ~]$ [ $a -eq $b ]
[abc@localhost ~]$ echo $?
1

字符串比较

[abc@localhost ~]$ [ "apple" = "banana" ]
[abc@localhost ~]$ echo $?
1
[abc@localhost ~]$ str1="apple"
[abc@localhost ~]$ str3="apple"
[abc@localhost ~]$ str2="banana"
[abc@localhost ~]$ [ $str1 = $str2 ]
[abc@localhost ~]$ echo $?
1
[abc@localhost ~]$ [ $str1 = $str3 ]
[abc@localhost ~]$ echo $?
0

if /else/fi条件语句

名词

条件允许程序员根据某些条件或事件在shell脚本中实现决策。

我们所指的条件语句是:if、then、else。

案例1-比较大小

举个例子:if num1大于num2,then 就打印num1大于2,else 如果不大于,就打印其他信息。

[abc@localhost ~]$ vi num.sh
#!/bin/bash
num1=100
num2=200if [ $num1 -gt $num2 ];thenecho "$num1 greater than num2"
elseecho "$num1 less than or equal $num2"
fi

注意fi 是关闭if条件块的关键词;注意中括号两边的间距,没有空间,就不能执行。

[abc@localhost ~]$ chmod +x num.sh
[abc@localhost ~]$ ./num.sh
100 less than or equal 200

案例2-判断目录是否存在

查看当前目录下是否有mysql目录

[abc@localhost ~]$ vi mysql.sh
#!/bin/bashdirectory="./mysql"if [ -d $directory ]; thenecho "$directory is exist!!!"
elseecho "$directory is not exist!"
fi

执行结果:

[abc@localhost ~]$ sh mysql.sh
./mysql is not exist!
[abc@localhost ~]$ mkdir mysql
[abc@localhost ~]$ sh mysql.sh
./mysql is exist!!!

案例3-嵌套if/else

[abc@localhost ~]$ cat nested.sh
#!/bin/bash# 这是一个嵌套if else脚本,while循环让用户循环选择字段。# 定义一个标志
choise=0# 定义一个函数,打印用户可选择的序号 字段
function p1() {echo "1. BeiJing"echo "2. ShangHai"echo "3. ChangSha"echo "请选择你喜欢城市的序号[1-3]"
}# 当choise为0的时候就循环
while [ $choise -eq 0 ]; dop1read choiseif [ $choise -eq 1 ]; thenecho "你选择的是:Beijing"elseif [ $choise -eq 2 ]; thenecho "您的选择是: ShangHai"elseif [ $choise -eq 3 ]; thenecho "您的选择是:ChangSha"elseecho “请输入合法的字段”choise=0fififi
done

执行结果:

[abc@localhost ~]$ sh nested.sh
1. BeiJing
2. ShangHai
3. ChangSha
请选择你喜欢城市的序号[1-3]
1
你选择的是:Beijing[abc@localhost ~]$ sh nested.sh
1. BeiJing
2. ShangHai
3. ChangSha
请选择你喜欢城市的序号[1-3]
4
“请输入合法的字段”
1. BeiJing
2. ShangHai
3. ChangSha
请选择你喜欢城市的序号[1-3]
3
您的选择是:ChangSha[abc@localhost ~]$ sh nested.sh
1. BeiJing
2. ShangHai
3. ChangSha
请选择你喜欢城市的序号[1-3]
2
您的选择是: ShangHai

案例4-if/elif.../else/fi

[abc@localhost ~]$ cat elif.sh
#!/bin/bash# 通过用户输入两个数值,分别赋值给num1、num2,然后来比较大小
read num1 num2if [ $num1 -eq $num2 ]; thenecho "Both values are equal!"
elif [ $num1 -gt $num2 ]; thenecho "num1 values is greater then num2"
elseecho "num1 values is less then num2"
fi

执行结果:

[abc@localhost ~]$ sh elif.sh
11 22
num1 values is less then num2
[abc@localhost ~]$ sh elif.sh
11 11
Both values are equal!
[abc@localhost ~]$ sh elif.sh
6 2
num1 values is greater then num2

优化backup.sh

执行完tar备份之后,要比较源数据的文件数量和备份后tar包的文件的数量。

#!/bin/bash# this bash script is backup a general user's home directory to /tmp/user=$(whoami)
input=/home/$user
output=/tmp/${user}_home$(date +%Y-%m-%d_%H%M%S).tar.gzfunction total_files {find $1 -type f | wc -l
}function total_directory {find $1 -type d | wc -l
}function total_archived_directory {tar -tzf $1 | grep /$ |wc -l
}function total_archived_files {tar -tzf $1 | grep -v /$ | wc -l
}tar czf $output $input 2> /dev/nullsrc_files=$( total_files $input )
src_directory=$( total_directory $input )arch_files=$( total_archived_files $output )
arch_directory=$( total_archived_directory $output )echo "Files to be include: $src_files"
echo "Directories to be include: $src_directory"
echo "Files archived: $arch_files"
echo "Directories archived: $arch_directory"if [ $src_files -eq $arch_files ];thenecho "Backup of $input completed!!!"echo "Details about the output backup file:"ls -l $output
elseecho "Backup of $input Failed!!!"
fi  

bash文件测试

bash文件测试列表

指令 描述
-b filename 块特殊文件
-c filename 特殊字符文件
-d directory name 检查目录是否存在
-e filename 检查文件是否存在
-f filename 检查是否存在常规文件,而不是目录
-G filename 检查文件是否存在,是否有效组ID
-g filename 如果文件存在并设置了组id,则为true。
-k filename Sticky bit
-L filename 符号链接 Symbolic link
-O filename 如果文件存在并有效用户id,则为true。
-r filename 检查文件是否可读
-S filename 检查文件是否为socket
-s filename 检查文件大小是否为非零
-u filename Check if file set-ser-id bit is set
-w filename 检查文件是否可写
-x filename 检查文件是否可执行

例子

[abc@localhost ~]$ vi file-test.sh
#!/bin/bash# this is a test file exist ? yes/nofile="./testtest.txt"
if [ -e $file ]; thenecho "File exists!"
elseecho "File does not exists"
fi

脚本执行结果:

[abc@localhost ~]$ /bin/bash file-test.sh
File does not exists
[abc@localhost ~]$ touch testtest.txt
[abc@localhost ~]$ /bin/bash file-test.sh
File exists!

位置参数

名词

到目前为止,我们的备份脚本看起来很棒。我们可以计算结果压缩备份文件中包含的文件和目录的数量。此外,我们的脚本还促进了一个健全检查,以确认所有文件已正确备份。缺点是我们总是被迫备份当前用户的目录。如果脚本足够灵活,允许系统管理员仅通过将脚本指向主目录就可以备份所选系统用户的主目录,那就太好了。

当使用bash位置参数时,这是一个相当容易的任务。位置参数通过命令行参数分配,并可在脚本中访问,如$1, $2...$N变量。在脚本执行期间,在程序名称之后提供的任何附加项都被视为参数,并在脚本执行期间可用。

$、$# 、$*

考虑以下示例:

[abc@localhost ~]$which bash > position.sh
[abc@localhost ~]$ vi position.sh
#!/usr/bin/bashecho $1 $2 $4
echo $#
echo $*
[abc@localhost ~]$ /bin/bash position.sh 1 2 3 4
1 2 4
4
1 2 3 4
[abc@localhost ~]$ /bin/bash position.sh one two three four
one two four
4
one two three four

脚本详细:

#!/usr/bin/bash
echo $1 $2 $4
echo $#
echo $*

第二行代表打印:第一个、第二个、第四个 位置参数

第三行代表打印:$#提供参数的总和

第四行代表打印:$*提供的所有参数

优化backup.sh

使用位置参数知识,让我们现在改进我们的backup.sh脚本来接受命令行中的参数。实现让用户决定哪个目录将被备份。如果用户在脚本执行期间没有提交任何参数,默认情况下,该脚本将备份当前用户的主目录。新脚本如下:

#!/bin/bash
# this bash script is used to backup a user's home directory if [ -z $1 ];thenuser=$(whoami)
elseif [ ! -d "/home/$1" ]; thenecho "Requested $1 user home directory doesn't exist!"exit 1fiuser=$1
fi# define default backup directory and tar.gz files
input=/home/$user
output=/tmp/${user}_home_$(date +%Y-%m-%d_%H%M%S).tar.gz# count the number of source files
function total_files {find $1 -type f | wc -l
}
# count the number of source directory
function total_directory {find $1 -type f | wc -l
}# count the number of archived files
total_archived_files() {tar -tzf $1 | grep /$ | wc -l
}
# count the number of archived directory
total_archived_directory() {tar -tzf $1 | grep -v /$ | wc -l
}tar -czf $output $input 2> /dev/nullsrc_files_num=$( total_files $input )
src_directory_num=$( total_directory $input)arch_files_num=$( total_archived_files $output)
arch_directory_num=$( total_archived_directory $output)echo "files to be included: $src_files_num"
echo "directory to be included: $src_directory_num"echo "files to be included of archived: $arch_files_num"
echo "directory to be included of archived: $arch_directory_num"if [ $src_files_num -eq $arch_files_num ]; thenecho "Backup of $input complete!!"echo "Deatils about the output backup file:"ls -l $output
elseecho "Backup of $input Failed!!!"
fi

脚本执行结果:

#不加参数执行,默认备份当前用户主目录
[abc@localhost ~]$ ./backup.sh
files to be included: 21
directory to be included: 21
files to be included of archived: 1
directory to be included of archived: 21
Backup of /home/abc Failed!!!
#加参数指定用户abc,备份abc用户的主目录
[abc@localhost ~]$ ./backup.sh abc
files to be included: 21
directory to be included: 21
files to be included of archived: 1
directory to be included of archived: 21
Backup of /home/abc Failed!!!
#加参数指定用户aaa,aaa用户不存在,报错了。
[abc@localhost ~]$ ./backup.sh aaa
Requested aaa user home directory doesn't exist!

bash 循环

loop名词

到目前为止,本分脚本功能和可用性已经大大提高,通过位置参数我们可以指定备份 某个用户的主目录,我们可以轻松的备份任何用户目录。

当我们需要每天备份多个用户目录时,就会变得重复、乏味、费时。

这个时候我们可以用循环构造来迭代任何给定数量的任务。

有三种基本循环类型。

for循环

for循环用于迭代列表中任意数量的提供项的任何给定代码。让我们从一个简单的for循环示例开始:

[abc@localhost ~]$ for i in 1 2 3; do echo $i; done
1
2
3

上边的for循环使用了echo 命令打印所以项目。使用分号允许在当行执行for循环。将上面的for循环变成bash脚本,如下:

[abc@localhost ~]$ vi loop.sh
#!/bin/bashfor i in 1 2 3; doecho $i
done
~       

for循环由四个shell保留字段组成:for、in、do、done。

因此,上述代码可以理解为:for 循环in在列表中的每一项,然后将每一项临时赋值给变量i,然后doecho $i 打印到stdout,直到列表循环完才结束done

for循环例子

计算文本里边每行的字符数量

[abc@localhost ~]$ cat items.txt
abc
abcd
abcdef
a
[abc@localhost ~]$ for i in $( cat items.txt ); do echo -n $i | wc -c; done
3
4
6
1

while循环

while循环在给定的条件下工作。它将继续执行封闭的代码。当条件为true的时候,do 执行代码,当条件变为false就终止done

[abc@localhost ~]$ vi while.sh
#!/bin/bashcounter=0
while [ $counter -lt 3 ]; dolet counter+=1echo $counter
done

代码详解:

定义变量:counter

while定义条件:当变量counter小于3的时候,条件为true。在每次循环迭代期间,使用let将counter自加1,然后打印变量counter。

done:当条件变成false 结束循环

脚本执行结果:

[abc@localhost ~]$ sh while.sh
1
2
3

until循环

until循环和while循环完全相反的操作。利用until、do、done,循环直到条件从false变成true就结束。

[abc@localhost ~]$ vi until.sh
#!/bin/bashcounter=6
until [ $counter -lt 3 ]; dolet counter-=1echo $counter
done

代码详解:

定义变量:counter

until定义条件:当变量counter不小于3的时候,条件为false。在每次循环迭代期间,使用let将counter自加1,然后打印变量counter。

done:当条件变成true 结束循环

脚本执行结果:

[abc@localhost ~]$ sh until.sh
5
4
3
2

优化backup.sh

之前的备份脚本每次执行备份一个用户的目录。我们可以通过位置参数给脚本提供多个用户,通过for循环去执行备份命令。如下:

#!/bin/bash# this bash script is used to backup one or more user's home directoryfunction backup {if [ ! -d "/home/$1" ]; thenecho "resquested $1 user home directory does't exist."exit 1fiuser=$1input=/home/$useroutput=/tmp/${user}_home_$(date +%Y-%m-%d_%H%M%S).tar.gzfunction total_files {find $1 -type f | wc -l}function total_directories {find $1 -type d | wc -l}function total_arch_files {tar -xvf $1 | grep -v /$ | wc -l    }function total_arch_directories {tar -xvf $1 | grep /$ | wc -l    }tar -czf $output $input 2> /dev/null# count the number of src_files and src_directorysrc_files=$( total_files $input )src_directories=$( total_directories $input )arch_files=$( total_arch_files $output )arch_directories=$( total_arch_directories $output )echo "######## $user ########"echo "Src_files to be included: $src_files"echo "Src_directories to be included: $src_directories"echo "Arch_files to be included: $arch_files"echo "Arch_directories to be included: $arch_directories"if [ $src_files -eq $arch_files ]; thenecho "--------------------------------------"echo "Backup user:<$user> home completed!!!"echo "Details about the output backup files:"ls -l $outputelseecho "Backup user:<$user> home Failed!!!"fi
}for directory in $*; dobackup $directory
done; 

该脚本最好用root用户去执行,因为普通用户备份别的用户home目录会存在权限问题。但该脚本并不能备份root的home目录。脚本执行结果:

[root@localhost ~]# sh loop_backup.sh aaa bbb ccc
######## aaa ########
Src_files to be included: 4
Src_directories to be included: 1
Arch_files to be included: 4
Arch_directories to be included: 1
--------------------------------------
Backup user:<aaa> home completed!!!
Details about the output backup files:
-rw-r--r-- 1 root root 519 Jul 17 04:11 /tmp/aaa_home_2018-07-17_041134.tar.gz
######## bbb ########
Src_files to be included: 5
Src_directories to be included: 2
Arch_files to be included: 5
Arch_directories to be included: 2
--------------------------------------
Backup user:<bbb> home completed!!!
Details about the output backup files:
-rw-r--r-- 1 root root 573 Jul 17 04:11 /tmp/bbb_home_2018-07-17_041134.tar.gz
######## ccc ########
Src_files to be included: 6
Src_directories to be included: 3
Arch_files to be included: 6
Arch_directories to be included: 3
--------------------------------------
Backup user:<ccc> home completed!!!
Details about the output backup files:
-rw-r--r-- 1 root root 606 Jul 17 04:11 /tmp/ccc_home_2018-07-17_041134.tar.gz

select语句

select表达式是bash的一种扩展应用,擅长于交互式场合。

语法:

select var in ... ; dobreak;done

例子:

[abc@localhost ~]$ vi select.sh
#!/bin/bashPS3="请选择你最喜欢城市的序号:"select word in "北京" "上海" "杭州" "长沙"; doecho "您选择的是:$word"break
done

执行结果:

[abc@localhost ~]$ sh select.sh
1) 北京
2) 上海
3) 杭州
4) 长沙
请选择你最喜欢城市的序号:1
您选择的是:北京

case语句

case语句为多选择语句。可以用case语句来匹配一个值与一个模式,如果匹配成功,执行相匹配的命令。

case语法:

case 值 in
模式1)command1command2...;;  # 两个分号代表结束
模式2)command1command2...;;
esac

如上:case的值后面必须是in,每个模式必须以 右括号结束,取值可以是变量或常数。匹配某一模式后,就会执行模式下的所有命令 直到 ;; 。

取值一旦匹配了某个模式,就不会再去匹配其他模式。如果没有匹配到,就会用 * 捕获该值,再执行后面的命令。

case语句最后要有一个结束标记:esac 。

例子:

[abc@localhost ~]$ cat case.sh
#!/bin/bashecho "请输入你喜欢的城市:"
echo "1) 北京"
echo "2) 上海"
echo "3) 杭州"
echo "4) 长沙"read numcase $num in
1) echo "你选择的是北京"echo 1;;
2)echo "你选择的是上海"echo 2;;
3)echo “你选择的是杭州”;;
4)echo "你选择的是长沙";;
*)echo "输入错误!请选择[1-4]城市对应的序号!";;
esac

执行结果:

[abc@localhost ~]$ sh case.sh
请输入你喜欢的城市:
1) 北京
2) 上海
3) 杭州
4) 长沙
1
你选择的是北京
1
[abc@localhost ~]$ sh case.sh
请输入你喜欢的城市:
1) 北京
2) 上海
3) 杭州
4) 长沙
3
“你选择的是杭州”
[abc@localhost ~]$ sh case.sh
请输入你喜欢的城市:
1) 北京
2) 上海
3) 杭州
4) 长沙
5
输入错误!请选择[1-4]城市对应的序号!

算术运算

整数运算

let运算命令

[root@ceshi ~]# vi let.sh
#!/bin/bash
num1=2
num2=3
let result=num1+num2
echo $result运行:
[root@ceshi ~]# /bin/bash let.sh
5
  1. 自加操作 let num1++

  2. 自减操作 let num1--

  3. 简写形式:let no+=10let ; let no-=20

    等同于:let no=no+10; let no=no-20

操作符[]运算方法

[root@ceshi ~]# vi fangkuohao.sh
#!/bin/bash
num1=2
num2=3
result=$[$num1+num2]
echo $result运行:
[root@ceshi ~]# /bin/bash fangkuohao.sh
5

注:使用方法和let相似,在[]中可以使用$前缀

(())运算方法

[root@ceshi ~]# vi xiaokuohao.sh
#!/bin/bash
n1=2
n2=3
result=$((n1+n2))
echo $result

expr运算方法

[root@ceshi ~]# expr 2 + 3
5
[root@ceshi ~]# num1=5
[root@ceshi ~]# r=$(expr $num1 + 5)
[root@ceshi ~]# echo $num1
5
[root@ceshi ~]# echo $r
10

expr的常用运算符

  • +
  • -
  • *
  • /
  • % 取模运算,也叫取余

精密计算

高级运算工具:bc

它可以执行浮点运算和一些高级函数

[root@ceshi ~]# echo "1.25*3" | bc
3.75

设定小数精度(也就是小数点显示几位)

scale=2 代表小数点显示2位

[root@ceshi ~]# echo "scale=2;7/3" | bc
2.33

进制转换

十进制转二进制:
[root@ceshi ~]# a=192
[root@ceshi ~]# echo "obase=2;$a" |bc
11000000二进制转十进制:
[root@ceshi ~]# b=11000000
[root@ceshi ~]# echo "obase=10;ibase=2;$b"|bc
192

计算平方和平方根

求2的三次方:
[root@ceshi ~]# echo "2^3"|bc
8求100的平方根
[root@ceshi ~]# echo "sqrt(100)"|bc
10

trap 命令

用于指定在接收到信号后将要采取的动作,常见的用途是在脚本程序被中断时完成清理工作、忽略ctrl+c。

当脚本在执行的时候,在终端驶入ctrl+c,trap会接收到消息,并执行相关trap命令。如下:

[abc@localhost ~]$ cat traptrap.sh
#!/bin/bash# difine bash trap command
trap bashtrap INT# clear screen command
clear;# define trap function:bashtrap, bashstrap is executed when CTRL-C is pressed;
bashtrap()
{echo "bash trap detected "CTRL+C" when script is executed. "
}# for loop from 1/10 to 10/10
for a in `seq 1 10`; doecho "$a/10 to exit"sleep 1;
doneecho "exit bash trap example!"

当脚本在执行的时候,在终端输入ctrl+c,trap到之后会执行bashtrap函数。执行结果如下:

[abc@localhost ~]$ sh traptrap.sh
1/10 to exit
2/10 to exit
^Cbash trap detected CTRL+C when script is executed.
3/10 to exit
^Cbash trap detected CTRL+C when script is executed.
4/10 to exit
^Cbash trap detected CTRL+C when script is executed.
5/10 to exit
6/10 to exit
7/10 to exit
^Cbash trap detected CTRL+C when script is executed.
8/10 to exit
9/10 to exit
10/10 to exit
exit bash trap example!

数组

详见:第四章 数组、关联数组和别名使用

https://blog.51cto.com/506554897/2114414

声明简单bash数组

[abc@localhost ~]$ vi array.sh
#!/bin/bash# 声明数组array,并赋值3个元素
array=( "Debian linux" "redhat linux" "ubuntu linux")# 获得数组array里边有多少个元素
elements=${#array[@]}# 通过for循环数组里边的元素的索引,并通过数组索引打印每个元素
for (( i=0;i<$elements;i++)); doecho ${array[${i}]}
done

执行结果:

[abc@localhost ~]$ sh array.sh
Debian linux
redhat linux
ubuntu linux

将文件读入bash数组

[abc@localhost ~]$ cat read.sh
#!/bin/bash# 这是一个读取文件每行内容到数组的脚本。# 声明一个变量array
declare -a array# 用stdin链接文件记录器10
exec 10<&0# 将stdin替换为作为第一个参数提供的文件。
exec < $1# 用while循环将文件每行内容作为一个元素写入数组
# count用作数组的下标
let count=0
while read line; doecho "line text is: $line"array[$count]=$line((count++))
done# 打印数组的元素总个数,@和* 这里是一个意思 都是所有
echo "count the number of array elements: ${#array[@]}"
# 打印数组的所有元素
echo "array content is: ${array[*]}"# 从filedescriptor 10恢复stdin; filedescriptor:文件记录器
# 关闭filedescriptor 10
exec 0<&10 10<&-

declare 声明变量和显示变量 详见:第二十八章 声明和显示shell变量:declare命令

执行结果:

[abc@localhost ~]$ cat a.txt
welcome
to
china
[abc@localhost ~]$ sh read.sh a.txt
line text is: welcome
line text is: to
line text is: china
count the number of array elements: 3
array content is: welcome to china
[abc@localhost ~]$ cat b.txt
大母鸡 小母鸡
母鸡 母鸡
老母鸡
[abc@localhost ~]$ sh read.sh b.txt
line text is: 大母鸡 小母鸡
line text is: 母鸡 母鸡
line text is: 老母鸡
count the number of array elements: 3
array content is: 大母鸡 小母鸡 母鸡 母鸡 老母鸡

bash 引用-转义

转义元字符

\ 反斜杠就是转义特殊符号。

转义元字符就是抑制元字符的特殊含义,因此元字符将被bash逐字逐句的阅读。

语法:\特殊字符

[abc@localhost ~]$ vi escape.sh
#!/bin/bash var1="Bash Script"echo "$var1"
echo "\$var1"

执行结果:

[abc@localhost ~]$ sh escape.sh
Bash Script
$var1

单引号

bash中的单引号 抑制每个元字符的特殊含义,元字符将被逐字逐句读取。

即使单引号被反斜杠转义,也不可能在两个单引号中使用另一单引号。

[abc@localhost ~]$ vi quotes.sh
#!/bin/bashvar2="Bash Script"echo "$var2"echo '$var2 "$var2"'

执行结果:

[abc@localhost ~]$ sh quotes.sh
Bash Script
$var2 "$var2"

双引号

双引号将抑制除了 `$ \ `` 以外的每个元字符的特殊含义。

任何其他元字符都会被逐字逐句读取。

在双引号中可以使用单引号。如果需要在双引号中使用双引号,可以使用"\" 进行转义。

[abc@localhost ~]$ vi double-quote.sh
#!/bin/bashvar3="Hello world"echo "$var3"echo "这是一个打招呼的词语:$var3"
echo "这是一个到招呼的词语: \"$var3\", time is `date +"%Y-%m-%d_%H:%M:%S"` "

执行结果如下:

[abc@localhost ~]$ sh double-quote.sh
Hello world
这是一个打招呼的词语:Hello world
这是一个到招呼的词语: "Hello world", time is 2018-07-19_16:35:23 

ANSI-C 类型 引用

ANSI-C :ANSI C是由美国国家标准协会(ANSI)及国际标准化组织(ISO)推出的关于C语言的标准。

在ANSI-C 类型的引用中,用“\” 转义的字符将按照ANSI-C标准获得特殊的含义。

转义字符 描述
\a alert 警报
\b backspace 退格键
\e 一个转义字符
\f form feed 换页符
\n newline 换行
\r carriage return 回车
\t horizontal tab 水平制表符
\v vertical tab 垂直制表符
\\ 反斜杠
\' 单引号
\nnn 字符的八进制值(见[http:/www.asciitable.com/ASCII表])
\xnn 字符的十六进制值(见[http:/www.asciitable.com/ASCII表])

ANSI-C 引用语法:$‘

[abc@localhost ~]$ vi ansic.sh
#!/bin/bashecho $'http://www.abc.com\nmai\x40abc.com'[root@docker-2 ~]# vi a.sh
#!/bin/bashecho $'http://www.baidu.com\nmail\x40baidu.com'

执行结果:

[abc@localhost ~]$ sh ansic.sh
http://www.abc.com
mai@abc.com[root@docker-2 ~]# sh a.sh
http://www.baidu.com
mail@baidu.com
本教程pdf下载地址:http://down.51cto.com/data/2451862
下载内容包含笔者29章节的 shell学习笔记
也可参考shell链接:http://tldp.org/LDP/abs/html/

转载于:https://blog.51cto.com/506554897/2173695

初学者编写bash脚本教程相关推荐

  1. bashsupport插件_如何用 bash-support 插件将 Vim 编辑器打造成编写 Bash 脚本的 IDE

    IDE(集成开发环境)就是这样一个软件,它为了最大化程序员生产效率,提供了很多编程所需的设施和组件. IDE 将所有开发工作集中到一个程序中,使得程序员可以编写.修改.编译.部署以及调试程序. 在这篇 ...

  2. 编写Bash脚本实现使用FFmpeg批量合并视频

    前言 使用FFmpeg能够很方便的合并同分辨率的视频.很多时候我们可能需要批量化合并视频,此时可以通过编写Bash脚本来实现. 实现 批量合并视频 合并三个文件夹中的视频结果: result_path ...

  3. 编写Bash脚本获取文件夹下文件名和扩展名

    前言 很多时候我们可能需要获取文件夹下的文件名和扩展名用于任务处理,此时可以通过编写Bash脚本来实现. 实现 result_path1=task1 result_path2=task2 concat ...

  4. 《Bash 脚本教程》免费发布啦,开源!

    一共写了 20 节,Bash 脚本编程的主要语法,都包括在内了,日常使用应该足够.也欢迎初学者使用这个教程,学习 Bash. 这个教程是开源的,你可以克隆它的代码仓库,放在本地,也可以提交 issue ...

  5. Bash脚本教程之脚本除错

    目录 常见错误 bash的-x参数 环境变量 LINENO FUNCNAME BASH_SOURCE BASH_LINENO 常见错误 编写 Shell 脚本的时候,一定要考虑到命令失败的情况,否则很 ...

  6. 使用VScode调试与编写bash脚本

    有时bash脚本会被混淆,或者脚本中变量比较多的时候,用调试工具就会方便又美观,而Linux平台为数不多的调试工具中比较卓越的就是Vscode调试平台. 使用vscode调试linux bash时,b ...

  7. Bash脚本教程之函数

    目录 简介 参数变量 return 命令 全局变量和局部变量,local 命令 简介 函数(function)是可以重复使用的代码片段,有利于代码的复用.它与别名(alias)的区别是,别名只适合封装 ...

  8. Bash脚本教程之字符串操作

    目录 字符串的长度 子字符串 搜索和替换 改变大小写 字符串的长度 获取字符串长度的语法如下. ${#varname} 下面是一个例子. $ myPath=/home/cam/book/long.fi ...

  9. python编写arcgis脚本教程_ArcGIS二次开发(1)arcpy简介及编写一个自己的脚本

    1.1.前言 在数据处理过程中,一般需要做一些数据的整理,如图层合并.裁剪等工作.虽然在ArcMap中也有提供对应的工具(批量处理.建模等),但对于多个步骤之前的衔接及其它特殊处理存在困难,本系列文章 ...

最新文章

  1. Java面试查漏补缺
  2. 源码|批量执行invokeAll()多选一invokeAny()
  3. 使用 C++ 的 StringBuilder 提升 4350% 的性能
  4. 关于计算机和人物的英语短文,人脑和电脑英语作文
  5. WebRTC通话质量调优:三个弱网模拟测试工具的使用与对比
  6. 2018年最新的single-cell-RNA-seq analysis repositories
  7. 单链表进阶学习 三段
  8. matlab的cylinder函数,matlab cylinder怎么用,Matlab cylinder 函数
  9. 网狐荣耀6701/6801服务端 子游戏编译 部署
  10. 关于智能制造的思考——以中航工业为例
  11. 台式计算机m9870t,新闻中心 ——驱动之家:您身边的电脑专家
  12. 模拟电路——阻容耦合放大电路
  13. 让人苦笑不得的基带版本未知错误
  14. PuTTY key format too new怎么解决?
  15. [Idea] Add Abbreviation的使用
  16. element日历(Calendar)排班
  17. iphone icloud无法载入储存空间信息
  18. 【go-zero】go-zero 敏感词过滤 整合 开源过滤敏感词插件 sensitive
  19. 清北学堂 2017-10-05
  20. 计算机视觉学习路线—计算机视觉入门必读的20本书

热门文章

  1. 使用自动编码器(Autoencoder)及其变体进行特征学习
  2. 设计师常用素材+学习视频,记得收好啦
  3. 企业应用业务需求变化的分析与应对-业务需求中的逻辑
  4. c++美发店管理系统设计_急求一份美发店管理系统设计c++程序代码?求大神帮忙,谢谢!!!!...
  5. python文本替换
  6. 简单高效在线APP原型工具
  7. python工具栏消失_解决Jupyter notebook更换主题工具栏被隐藏及添加目
  8. 高可用ftp、sftp服务器的搭建
  9. mysql安装5.1.57_数据库mysql57安装
  10. 鸿蒙系统吃鸡用转区吗,吃鸡:即将迎来转区功能,4种转区方式选择,玩家可自己决定!...