Shell 编程入门

Shell 是一个应用程序,充当用户界面和脚本解释器,是用户使用 Linux 系统的桥梁。大多数 Linux 发行版将 Bash 作为默认的 Shell 程序,本文将以 Bash 为例,介绍如何在 Linux 环境进行 Shell 编程。Shell 脚本是一种以 Shell 程序为解释器的脚本程序。由于习惯的原因,业界所说的"Shell 编程"都是指编写 Shell 脚本,不是指 Shell 程序扩展开发。
1 一个简单的 Hello World 脚本
创建一个名为 hello.sh 的文本文件,输入一下内容并保存退出。
# Hello world demo
#!/bin/bash
echo 'Hello world!'
第一行以 # 开头表示注释,说明脚本的功能;
第二行以 #! 开头指定使用的 Shell 程序;
最后一行使用 echo 命令向屏幕打印 "Hello World!"。
shell脚本的执行有两种方式,一种是显示使用 Shell 程序执行脚本,另一种方式是给脚本添加可执行权限后直接执行脚本。
# 1. 使用 Shell 程序执行脚本
# 使用 bash 执行脚本
bash hello.sh
# 2. 先为脚本添加可执行权限,然后直接执行脚本
chmod +x hello.sh
./hello.sh
其中第二种方式直接执行脚本需要指定脚本所在路径,否则将在 Path 环境变量中检索脚本。
2 变量
2.1 定义变量
Shell 脚本中使用如下方式定义变量,注意变量名和等号之间不能有空格。
# 直接定义变量
my_name="Bob"
# 在语句中给变量赋值,例如在 for 循环中给变量赋值
for file in `ls /etc`
for file in ./xxx
2.2 使用变量
使用一个定义过的变量,只需要在变量名前面加美元符号即可,例如:
my_name="Bob" # 定义字符串变量 my_name
echo $my_name # 打印 my_name 的值
# 可以使用 export 将局部变量转换为全局变量,可以使用 env 命令查看所有的全局变量
export my_name
2.3 重新定义变量
直接给已经定义的变量赋值就好。
my_name="Bob"
echo $my_name
my_name="Tom" # 重新给变量赋值
echo $my_name
3 注释
以 # 号开头的行就是注释,Shell 里没有多行注释,只能每一行加一个 #,如下:
# 这是一行注释
# 这也是一行注释
4 字符串
Shell 中的字符串可以用单引号,可以用双引号,也可以不用引号。
4.1 单引号字符串
str='This is a string.'
echo $str
my_name="Bob"
str='I am $my_name'
echo $str
单引号字符串的限制:
- 单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的
- 单引号字符串中不能出现单引号,单引号中的单引号将会被忽略(对单引号使用转义字符也不行)
4.2 双引号字符串
my_name="Bob"
greeting="Hello, I am $my_name!\n"
echo $greeting
my_name="Tom"
greeting="Hello, I am ${my_name}!\n"
echo $greeting
双引号字符串里可以有变量
双引号字符串里可以出现转义字符
4.3 不加引号的字符串
不加引号的字符串不会将含有空格的字符串视为一个整体输出, 如果内容中有变量等,会先把变量解析出结果,然后在输出最终内容来,如果字符串中带有空格等特殊字符,则不能完整的输出,需要改加双引号,一般连续的字符串,数字,路径等可以用。
name="Bob"
# 不加引号字符串中的变量前不用加$
str=+++++name+++++
echo $str
4.4 反引号的字符串
如果需要调用命令的输出,或把命令的输出赋给变量,则命令必须使用反引号包含,这条命令才会执行。反引号的作用和 $(命令) 是一样的,但是反引号非常容易和单引号搞混,所以推荐使用 $(命令) 的方式引用命令的输出。
# date被当做字符串
echo "date"
# 输出日期时间
echo `date`
echo $(date)
4.5 字符串操作
字符串操作包括拼接字符串、获取字符串的长度、提取子串、查找子串等。
# 字符串拼接
my_name="Bob"
greeting="I am "$my_name"!"
echo $greeting
greeting="I am ${my_name}"
echo $greeting
# 获取字符串的长度
str="abcde"
echo ${#str} # 输出 5
# 提取子串 ${字符串名:起始索引:子串长度}
str="HUAWEI is a great company."
echo ${str:12:5} # 输出 great
# 查找子串
str="HUAWEI is a great company."
# 找出字母 H 的位置, 从 0 开始
echo $(expr index "$str" H) # 返回1
5 流程控制
和 Java 等语言不一样,Shell 的流程控制不可为空,如:
if (i == 0) {
System.out.println("i is equal to 0")
} else {
// do nothing...
}
在 Shell 中,这样写是不合法的,如果 else 分支没有语句执行,就不要写这个 else。
此外,Shell 中的方括号是一个可执行程序,在 CentOS/Ubuntu 中,它位于 /usr/bin 目录下:
ll /usr/bin/[
# 输出如下
-rwxr-xr-x 1 root root 55744 Apr 5 2024 '/usr/bin/['*
正因为方括号是一个可执行程序,方括号后面必须加空格,不能写成 if [$foo -eq 0]
5.1 if else
5.1.1 if
if condition
then
command1
command2
...
commandN
fi
写成一行(适用于终端命令提示符):
if `ps -ef | grep ssh`; then echo hello; fi
末尾的 fi 就是 if 倒过来拼写,后面还会遇到类似的情况。
5.1.2 if else
if condition
then
command1
command2
...
commandN
else
command
fi
5.1.3 if else-if else
if condition1
then
command1
elif condition2
command2
else
commandN
fi
5.2 for while
5.2.1 for
for var in item1 item2 ... itemN
do
command1
command2
...
commandN
done
写成一行:
for var in item1 item2 ... itemN; do command1; command2… done;
5.2.2 C 风格的 for
for (( EXP1; EXP2; EXP3 ))
do
command1
command2
command3
done
5.2.3 while
while condition
do
command
done
5.3.4 死循环
while :
do
command
done
# 或者
while true
do
command
done
# 或者
for (( ; ; ))
5.3.5 until
until condition
do
command
done
5.3.6 case 例子
case 的语法和 C family 语言差别很大,它需要一个 esac(就是 case 反过来)作为结束标记,每个 case 分支用右圆括号,用两个分号表示 break。
case "${opt}" in
"Install-Puppet-Server" )
install_master $1
exit
;;
"Install-Puppet-Client" )
install_client $1
exit
;;
"Config-Puppet-Server" )
config_puppet_master
exit
;;
"Config-Puppet-Client" )
config_puppet_client
exit
;;
"Exit" )
exit
;;
* ) echo "Bad option, please choose again"
esac
6 文件包含
可以使用 source 和 . 关键字,例如:
source ./function.sh
. ./function.sh
在 bash 里,source 和 . 是等效的,他们都是读入 function.sh 的内容并执行其内容,为了更好的可移植性,推荐使用第二种写法。
包含一个文件和执行一个文件一样,也要写这个文件的路径,不能光写文件名。
如果 function.sh 是用户传入的参数,如何获得它的绝对路径呢?方法是:
real_path=`readlink -f $1`#$1是用户输入的参数,如function.sh
. $real_path