本文为Linux Shell Scripting Tutorial (LSST) v2.0学习记录
第二章:开始shell脚本编程
本章节学习目标:
编写你第一个shell 程序
理解创建一个shell脚本的步骤
2.1 Bash shell(全称Bourne again shell)
有关bash的创建历史(来自维基百科):
Bourne shell是一个交互式的shell,由AT&T实验室的史蒂夫在1977年发布,位于大多数Unix系统上的/bin/sh,随着时间的发展,GNU计划的诞生伴随着shell的开发,这个时候1987年布莱恩编写了Bash,也就是Bourne again shell,总的来说,Bash虽然是一个满足POSIX规范的shell,但有很多拓展。
Bash就是shell,或者可以说是Linux系统的命令语言解释器:
- Bash式GNU计划的产物
- Linux的默认shell
- 反向兼容UNIX系统的sh
- 与Korn shell(ksh)以及C shell(csh)兼容
- 在多平台类似UNIX/dos/Windows都是可运行多个
BASH的提升特性
- 命令行编辑
- 命令行补全
- 无限大小的命令历史
- 提示控制
- 不限大小的array
- 以2-64为基数的整数运算
- ……
作者
- Brian J. Fox authored the GNU Bash shell, in 1987.
- Fox maintained Bash as the primary maintainer until 1993, at which point Chet Ramey took over.
- Chet Ramey is the current maintainer of the GNU Bourne Again Shell and GNU Readline.
下载
- bash式Linux的默认shell,最新版本可以在official website下载
- Bash home page
- Chet's home page
2.2 shell命令
bash shell有两种类型的命令:
- 内置命令
- 外部命令(在bin/目录里面的命令)
bash和命令类型
bash接受以下几种类型的命令:
- 别名(例如
ll
) - 关键词(例如
if
) - 函数(例如
genpassword
) - 内置命令(例如
pwd
) - 文件(例如/bin/date)
type命令
找出某个命令(ls)是内置命令还是外部命令:
new@Chevy-PC:~$ type ls
ls is aliased to `ls --color=auto'
# 显示为外部命令
找出某个命令(command)是内置命令还是外部命令:
new@Chevy-PC:~$ type -a history
history is a shell builtin
# 显示为内置命令
bash命令关键词以及内置命令
- JOB_SPEC &
- (( expression ))
- . filename
- [[:]]
- [ arg... ]
- expression
- alias
- bg
- bind
- builtin
- caller
- case
- command
- compgen
- complete
- continue
- declare
- dirs
- disown
- echo
- enable
- eval
- exec
- exit
- export
- false
- fc
- fg
- for
- getopts
- hash
- help
- history
- if
- jobs
- kill
- let
- local
- logout
- popd
- printf
- pushd
- pwd
- read
- readonly
- return
- select
- set
- shift
- shopt
- source
- suspend
- test
- time
- times
- trap
- true
- type
- typeset
- ulimit
- umask
- unalias
- unset
- until
- variables
- while
2.3 Hello, World! 第一个shell脚本
创建一个shell脚本你需要经过以下几个步骤:
- 利用一个文本编辑器(例如vi),将你的指令写入一个文本
- 保存该文本并退出
- 改变该文本的可执行权限
- 测试该脚本并放入你的生产环境
- 最简单的shell脚本就是一行代码告诉计算机执行一个命令
让我们来熟悉vi的操作并创建第一个shell script
# 打开一个文件
vi first_script.sh
# 进入编辑模式
# 按Esc键,然后按I键就进入编辑模式
# 进入命令模式
# 按Esc键就进入命令模式
# 保存文件
# 按Esc键然后输入:w
# 或者按Esc键然后输入:w first_script.sh
# 保存并退出文件
# 按Esc键然后输入:wq
# 或者按Esc键然后输入:x
# 跳到某一行
# 按Esc键然后输入:x, x代表第几行
# 搜索一个字符
# 按Esc键然后输入/str, str代表该字符
# 退出vi(不保存)
# 按Esc键然后输入:q
# 让我们来编辑第一个脚本,输入以下字符并保存退出
#!/bin/bash
echo "Hello, World!"
echo "Knowledge is power."
# 将该文件改为可以执行文件并执行
new@Chevy-PC:~$ chmod 777 first_script.sh
new@Chevy-PC:~$ ./first_script.sh
Hello, World!
Knowledge is power.
Shebang
在脚本的第一行,我们需要告诉系统用哪种程序来运行这个脚本,写法为#!/path_to_binaries
,我们一般称其为Shebang或者bang行[1]
在bash脚本里面第一行我们一般写成#!/bin/bash
,有些程序使用perl
来运行的话,就写成#!/bin/perl
一个#!/bin.sh
的脚本示例:
etc/init.d/policykit
#! /bin/sh
### BEGIN INIT INFO
# Provides: policykit
# Required-Start: $local_fs
# Required-Stop: $local_fs
# Default-Start: 2 3 4 5
# Default-Stop:
# Short-Description: Create PolicyKit runtime directories
# Description: Create directories which PolicyKit needs at runtime,
# such as /var/run/PolicyKit
### END INIT INFO
# Author: Martin Pitt <martin.pitt@ubuntu.com>
case "$1" in
start)
mkdir -p /var/run/PolicyKit
chown root:polkituser /var/run/PolicyKit
chmod 770 /var/run/PolicyKit
;;
stop|restart|force-reload)
;;
*)
echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2
exit 3
;;
esac
:
Shell注释
在脚本里面#
号后面的代码会被忽略,这行代码我们成为注释,注释可以帮助我们理解代码并且便于修改:
#!/bin/bash
# A Simple Shell Script To Get Linux Network Information
# Vivek Gite - 30/Aug/2009
echo "Current date : $(date) @ $(hostname)"
echo "Network configuration"
/sbin/ifconfig
多行注释可以使用HERE DOCUMENT 特性来进行注释:
#!/bin/bash
echo "Adding new users to LDAP Server..."
<<COMMENT1
Master LDAP server : dir1.nixcraft.net.in
Add user to master and it will get sync to backup server too
Profile and active directory hooks are below
COMMENT1
echo "Searching for user..."
设置脚本权限
一个刚编辑完的脚本无法直接执行,你需要使用chmod
命令给与其可执行权限或者直接调用对应的命令来执行,下面的操作具有同样的效果:
chmod +x script.sh && ./script.sh
chmod 777 script.sh && ./script.sh
sh script.sh
使用ls -l
可以查看一个文件的权限:
new@Chevy-PC:~$ ls -l first.sh
-rwxrwxrwx 1 new new 61 Jul 15 14:24 first.sh
更多chomd
的操作可以使用man chmod
查看
为什么不直接使用
scriptname
来调用脚本?为什么当工作目录$PWD
正好是scriptname
所在目录时也不起作用?因为一些安全原因,当前目录./
并不会被默认添加到用户的$PATH路径中。因此需要用户显式使用 ./scriptname 在当前目录下调用脚本。
脚本debug
在运行脚本的时候需要加上-x
参数或者-xv
参数,又或者是将shebang行改成#!/bin.bash-x
使用内置命令
bash shell提供了debug选项,可以使用set
命令打开或者关闭:
-
set -x
:当脚本被执行的时候展示命令及参数 -
set -v
:当脚本被读入的时候展示输入行 -
set -n
:读入命令但不执行(在检查脚本是否有变量名重合的时候)
#!/bin/bash
### Turn on debug mode ###
set -x
# Run shell commands
echo "Hello $(LOGNAME)"
echo "Today is $(date)"
echo "Users currently on the machine, and their processes:"
### Turn OFF debug mode ###
set +x
# Add more commands without debug mode
#!/bin/bash
set -n # only read command but do not execute them
set -o noexec
echo "This is a test"
# no file is created as bash will only read commands but do not executes them
本章节复习题
练习题1
按照下面代码写一个脚本并运行,观察输出:
# Script to print currently logged in users information, and current date & time.
clear
echo "Hello $USER"
echo -e "Today is \c ";date
echo -e "Number of user login : \c" ; who | wc -l
echo "Calendar"
cal
exit 0
输出结果:
Hello new
Today is Tue Jul 16 15:34:58 CST 2019
Number of user login : 0
Calendar
July 2019
Su Mo Tu We Th Fr Sa
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
练习题2
写一个程序,打印出你最喜欢的电影名称,同时在下一行打印出导演的名称(以下是我自己的答案):
#!/bin/bash
# print moive and director
set -e
my_favorite_moive="gone_with_the_wind"
moive_director="not_known"
echo -e "my_favorite_moive is \c";
echo $my_favorite_moive
echo -e "the moive's director is \c";
echo $moive_director
exit 0
练习题3
写一个shell脚本,打印你的名字,在用户按<ENTER>键之前保持等待状态:
#!/bin/bash
echo "Vivek Gite"
read -p "Press [Enter] key to continue..." fakeEnterKey
练习题4
列出十个内置命令和外部命令:
# builtin commands
history
break
cd
continue
eval
exit
grep
kill
# external commands
ls
gzip
练习题5
使用cd
命令进入/etc/init.d目录下,查看各种系统启动脚本:
new@Chevy-PC:/bin$ cd /etc/init.d/
new@Chevy-PC:/etc/init.d$ ll
total 156
-rwxr-xr-x 1 root root 2269 Apr 22 2017 acpid*
-rwxr-xr-x 1 root root 4335 Mar 23 2018 apparmor*
-rwxr-xr-x 1 root root 2802 Nov 21 2017 apport*
-rwxr-xr-x 1 root root 1071 Aug 22 2015 atd*
-rwxr-xr-x 1 root root 1232 Apr 19 2018 console-setup.sh*
-rwxr-xr-x 1 root root 3049 Nov 16 2017 cron*
-rwxr-xr-x 1 root root 937 Mar 18 2018 cryptdisks*
-rwxr-xr-x 1 root root 978 Mar 18 2018 cryptdisks-early*
-rwxr-xr-x 1 root root 2813 Nov 16 2017 dbus*
-rwxr-xr-x 1 root root 4489 Jun 29 2018 ebtables*
-rwxr-xr-x 1 root root 3809 Feb 15 2018 hwclock.sh*
-rwxr-xr-x 1 root root 2444 Oct 25 2017 irqbalance*
-rwxr-xr-x 1 root root 1503 Feb 22 2018 iscsid*
-rwxr-xr-x 1 root root 1479 Feb 16 2018 keyboard-setup.sh*
-rwxr-xr-x 1 root root 2044 Aug 16 2017 kmod*
-rwxr-xr-x 1 root root 695 Dec 3 2017 lvm2*
-rwxr-xr-x 1 root root 571 Dec 3 2017 lvm2-lvmetad*
-rwxr-xr-x 1 root root 586 Dec 3 2017 lvm2-lvmpolld*
-rwxr-xr-x 1 root root 2378 Jun 17 2018 lxcfs*
-rwxr-xr-x 1 root root 2240 Jun 6 2018 lxd*
-rwxr-xr-x 1 root root 2653 Jun 26 2018 mdadm*
-rwxr-xr-x 1 root root 1249 Jun 26 2018 mdadm-waitidle*
-rwxr-xr-x 1 root root 2503 Feb 22 2018 open-iscsi*
-rwxr-xr-x 1 root root 1846 Mar 22 2018 open-vm-tools*
-rwxr-xr-x 1 root root 1366 Jan 17 2018 plymouth*
-rwxr-xr-x 1 root root 752 Jan 17 2018 plymouth-log*
-rwxr-xr-x 1 root root 1191 Jan 18 2018 procps*
-rwxr-xr-x 1 root root 4355 Dec 13 2017 rsync*
-rwxr-xr-x 1 root root 2864 Jan 15 2018 rsyslog*
-rwxr-xr-x 1 root root 1222 May 22 2017 screen-cleanup*
-rwxr-xr-x 1 root root 3837 Jan 26 2018 ssh*
-rwxr-xr-x 1 root root 5974 Apr 21 2018 udev*
-rwxr-xr-x 1 root root 2083 Aug 16 2017 ufw*
-rwxr-xr-x 1 root root 1391 Jul 18 2018 unattended-upgrades*
-rwxr-xr-x 1 root root 1306 May 16 2018 uuidd*