Sehll 编程基础

当你查看此教程时,默认你具有 Linux 基础 和 计算机语言基础

一. shell 编程基础

1. 简述

在 Linux 中,Shell 可以称之为 ”壳“ ,Linux内核驱动着众多硬件,但我们不会直接去指挥硬件,而是在更抽象的 shell 上操作硬件,shell 是一个命令解释器,输入的 Linux 指令首先由 shell 进行解析,然后再转换为对内核的操作。

因为 Linux 是开源的,所有人都可以自行开发相应软件,所以 shell 也有不同的流行版本,有 Bourne Shell(sh)、Bourne Again shell(bash)、C Shell(csh)、K shell(ksh) 等,其中 sh 是 Unix 上标准的 shell,而在 Linux 上,bash 是使用最多的 shell 程序。本文档基于 bash 编写

Shell 既是一种命令语言(命令本身),又是一种程序设计语言(用命令来编写脚本),也是一种应用程序(作为解释器),这个应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务,连接了用户和 Linux 内核。

2. bash 脚本编写到执行过程

创建脚本文件,脚本文件的后缀默认为.sh

touch hello.sh

使用 vi 或 vim 命令进入编辑,命令:vim hello.sh,以下是 hello.sh 文件内容

#! /bin/bash
# 第一行需要标明这是对应什么 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)

echo 'hello'  # 将字符串在标准输出上打印出来

赋予脚本文件执行权限

# a+x 表示所有用户都将获得该文件的执行权限,将 x 改为 1 也可以达到相同效果
chmod a+x hello.sh

执行脚本的方式

1. ./*.sh
# 使用 './' 执行脚本文件,‘/’# 后为文件的路径
# 此时,hello.sh 必须拥有执行权限
# bash 进程创建子进程来执行 hello.sh 脚本
./hello.sh

2. sh *.sh(或 bash *.sh)
# root 和 user 可以通过 sh 执行脚本,即使该文件并没有执行权限
# bash 进程创建子进程来执行 hello.sh 脚本
sh hello.sh

3. source *.sh
# 一般用于从指定文件中读取和执行命令,使配置文件立刻生效而不需重启整台服务器
# 多文件编译时建议使用,其它情况下并没有什么不同
# bash 进程直接执行 hello.sh 脚本
source hello.sh

#----------------------------------------------------------------------
# 关于上述的创建子进程执行或直接执行,可以尝试同时使用 ./ 和 bash 方式执行脚本
# (注意:建议在脚本中添加一行 ’read‘,保持脚本一直在运行,否则 pstree 无法查看进程)
# 使用‘pstree -T’,关注 systemd/plasmashell/konsole 下的内容
# ???是否使用子进程和脚本的运行有什么关系
# 子进程无法读取 父进程 没有主动暴露(export)的变量
#----------------------------------------------------------------------

3. 有效期与环境配置文件

默认情况下,在 Shell 下的用户变量、别名等,只在此次登录中有效。关闭终端或注销账户后,设置就会回到初始值。Linux 上有几个环境配置文件,如果想要变量长期生效,需要将变量放到配置文件中,并使用source命令重新载入环境配置文件

全局环境变量配置文件:/etc/profile、/etc/bashrc

用户环境变量配置文件:/.bash_profile、/.bashrc

上述四个文件读取顺序:/etc/profile、/.bash_profile、/.bashrc、/etc/bashrc

如果有过手动配置 JDK 或者 Python 包的经历,或许你会熟悉其中的某个文件

4. 注释

Shell 中的注释只有一种单行注释,就是 '#'
多行注释只能由单行注释形成
#----------------------------------------------------
# 多行注释
#----------------------------------------------------

特殊的多行注释是将注释内容用花括号括起来形成一个函数,如果不调用,这个注释内容
就是安全的。
这种注释方式是为一些调试中的代码设计的
:<<EOF
注释内容...
注释内容...
注释内容...
EOF

:<<'
注释内容...
注释内容...
注释内容...
'

二. 变量、数组

1. 变量

Shell 语言是一种弱语言,声明变量时无需声明对象类型。Shell 有四种变量,分别是:用户自定义变量、环境变量、预定义变量(内部变量)、位置变量

用户自定义变量

#----------------------------------------------------
# 变量命名规则
#----------------------------------------------------
# 命名只能使用英文字母,数字和下划线,不能使用标点符号
[guyan@DrangonBoat ~]$ login@root=guyan
bash: login@root=guyan:未找到命令
# 声明时,中间不能有空格
[guyan@DrangonBoat ~]$ login root=guyan
bash: login:未找到命令
[guyan@DrangonBoat ~]$ loginroot = guyan
bash: loginroot:未找到命令 
# 首个字符不能以数字开头
[guyan@DrangonBoat ~]$ 1name=guyan
bash: 1name=guyan:未找到命
# 不能使用bash里的关键字,使用 help 命令查看关键字
help

#-----------------------------------------------------
# 正常的变量命名
#-----------------------------------------------------
[guyan@DrangonBoat ~]$ NAME=guyan
[guyan@DrangonBoat ~]$ echo $NAME
guyan
[guyan@DrangonBoat ~]$ NAME="guyan"
[guyan@DrangonBoat ~]$ echo $NAME
guyan
[guyan@DrangonBoat ~]$ NAME="gu yan"
[guyan@DrangonBoat ~]$ echo $NAME
gu yan
[guyan@DrangonBoat ~]$ echo ${NAME}???
gu yan???
[guyan@DrangonBoat ~]$ A="a2b" B=$A
[guyan@DrangonBoat ~]$ echo $A
a2b
[guyan@DrangonBoat ~]$ echo $B
a2b

#----------------------------------------------------
# 参数置换功能
#----------------------------------------------------
# 如果设置了参数,则用参数值置换变量的值,否则用 word 置换变量的值
变量=${参数:-word}
# 如果设置了参数,则用参数值置换变量的值,否则用 word 置换变量的值,且同时将参数置换为 word
变量=${参数:=word}
# 如果设置了参数,则用参数值置换变量的值,否则就显示 word 并从 shall 中退出,常用于出错指示
变量=${参数:?word}
# 如果设置了参数,则用 word 置换参数的值,否则不进行置换
变量=${参数:+word}

#----------------------------------------------------
# 删除变量
#----------------------------------------------------
unset 变量名

#----------------------------------------------------
# 只读变量
#----------------------------------------------------
myUrl="https://www.google.com"  # 赋初始值
readonly myUrl                  # 设置变量为只读变量
myUrl="https://www.runoob.com"  # 重新赋值变量
# 报错信息
bash: myUrl:只读变量

#----------------------------------------------------
# 字符串
#----------------------------------------------------
# 单引号里的任何字符都会原样输出,双引号里的变量才能被正常转化
[guyan@DrangonBoat ~]$ NAME=guyan
[guyan@DrangonBoat ~]$ SAY_HELLO='hello,$NAME'
[guyan@DrangonBoat ~]$ echo $SAY_HELLO
hello,$NAME
[guyan@DrangonBoat ~]$ SAY_HELLO="hello,$NAME"
[guyan@DrangonBoat ~]$ echo $SAY_HELLO
hello,guyan
# 获取字符串长度
[guyan@DrangonBoat ~]$ echo ${#NAME}
5
# 截取字符串(从字符串第 3 个字符开始截取 3 个字符)
[guyan@DrangonBoat ~]$ echo ${NAME:2:3}
yan

环境变量

系统启动后一直存在,所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候 shell 脚本也可以定义环境变量,环境变量一般存储在这几个文件中 /etc/profile、~/.bash_profile、~/.bashrc、/etc/bashrc,以下是一些环境变量的作用介绍

环境变量 作用
BASH 当前运行的 Shell 的实例的路径名
BASH_VERSINFO Shell 版本号
CDPATH 用于 cd 命令的搜索路径,“.”不用单独设置,永远被包含
COLUMNS 终端的列数
EDITOR 编辑器
HOME 用于保存当前用户主目录的完全路径名
HISTSIZE 指示当前的 bash 所使用的历史文件
HOSTNAME 主机名
IFS 设置内部字段分隔符,默认为空格、Tab 以及 换行符
LANG 语言相关的环境变量,多语言环境可以修改此环境变量
LINES 终端的行数
LOGNAME 当前用户的登录名
MALL 当前用户的邮件存放目录
OLDPWD 上一个工作目录
PATH 用于保存用冒号分隔的目录路径,决定了 Shell 将到哪些目录中寻找命令或程序,Shell 将按 PATH 变量中给出的顺序搜索这些目录,找到第一个与命令名称一直的可执行文件并执行
PS1 主提示符,root 用户的默认主题是符是 ‘#’,普通用户的默认提示符是 ‘$‘
PS2 默认的辅助提示符是 ‘>’,当用户在输入行的末尾输入“”然后按下 Enter 或 用户按下 Enter 但 Shell 判断用户正在输入的命令没有结束时,就会显示这个辅助提示符
PWD 当前工作目录的绝对路径名,该变量的值随 cd 命令的使用而变化
SECONDS 启动的秒数
SHELL 当前用户的 Shell 类型,也指出 Shell 解释器程序放在什么地方
TERM 终端的类型
UID 当前用户的识别码

预定义变量

预定义变量和环境变量类似,但用户只能使用,而不能重新定义预定义变量,所有的预定义变量都是由 $ 符和另一个符号组成的。

预定义变量 含义
$0 当前执行的进程名
$! 后台运行的最后一个进程的进程号 PID
$? 命令执行后返回的状态,即上一个命令的返回代码,用于检查上一个命令执行是否正确。命令退出状态为 0 表示
$* 表示所有位置参数(命令行参数)的值,即传递给程序的所有参数组成的字符串,如执行 test.sh a b c 命令后,$# 为 a b c
$# 表示所有位置参数(命令行参数)的值,即传递给程序的所有参数组成的字符串,如执行 test.sh a b c 命令后,$# 的值为 3
$$ 表示当前进程号(PID)
$- 记录当前设置的 Shell 选项,这些选项由 set 命令设置。如执行 echo $-  命令,输出的结果是 himBHs,其中包括字符 i 表示此 Shell 是交互的。可以通过 set 命令来设置或取消一个选项配置,如执行了 set -x 命令,$- 的值为 himxBHs,执行 set +x 命令,$- 的值为 himBHs
$@ 表示所有位置参数(命令行参数)的值,分别用双引号括起来。如执行 sh test.sh a b c 命令后,$@ 为 a b c

位置变量

位置变量是一种在调用 Shell 程序的命令行中按照各自的位置决定的变量,是在程序名之后输入的参数。

位置变量之间用空格分隔,Shell 取第一个位置变量替换程序文件中的 $1,第一个位置变量替换程序文件中的 $2,以此类推。$0 是一个特殊的变量,它的内容是当前这个 Shell 程序的文件名,所以 $0 不是一个位置变量,在显示当前所有位置变量时是不包括 $0 的。### 2. 转义字符

2. 数组

在 Shell 中,只有一维数组,用括号来表示数,数组元素只用空格分割

#---------------------------------------------------------------
# 定义数组
#---------------------------------------------------------------
# 定义方式1
array_name=(value1 value2 value3 value4)
# 定义方式2
array_name=(
value0
value1
value2
value3
)
# 定义方式3(数组下标不必连续,不限制下标范围,反正 shell 会对数组有默认的 id )
array_name[0]=value0
array_name[1]=value1
array_name[n]=valuen

#---------------------------------------------------------------
# 读取数组元素
#---------------------------------------------------------------
${数组名[下标]}
valuen=${array_name[n]}
# 使用 ‘@’ 符号获取数组中的所有元素
echo ${array_name[@]}

#---------------------------------------------------------------
# 获取数组/元素长度
#---------------------------------------------------------------
# ‘#’ 表示将要取参数的长度
# 取得数组元素的个数
length=${#array_name[@]}
# 取得数组中某个元素的长度
lengthn=${#array_name[n]}

三. shell 程序设计流程控制

1. 复合结构

Bash 中可以使用一对 “{}”” 或 “()”” 将多条命令组合在一起,使它们在逻辑上成为一条命令。

Bash 执行 “( )”” 中的命令时,会再创建一个子进程,然后由这个子进程去执行 “( )” 中的命令。如果不想让命令运行时对状态集合(如环境变量、位置参数等)的改变影响到下面语句的执行,就应该把这些命令放到 “( )” 中

注意:”{” 之后要有一个空格,”}” 之前要有一个分号

注意:”(” 之后的空格可有可无,”)” 之前的分号可有可无

2. 条件分支

1. if
#-------------------------------------------------------
# 基本格式
#-------------------------------------------------------
# if
if condition
then
    command1 
    command2
    ...
    commandN 
fi
# 在终端输入 if 语句可以参考以下格式
if [ $(ps -ef | grep -c "ssh") -gt 1 ]; then echo "true"; fi
# if else
if condition
then
    command1 
    command2
    ...
    commandN
else
    command
fi
# if else-if else
if condition1
then
    command1
elif condition2 
then 
    command2
else
    commandN
fi
#-------------------------------------------------------
# 例子
#-------------------------------------------------------
# 常用 test 语句与 if 语句混用作为判断依据
if [ "$a" -gt "$b" ]; then
    ...
fi


2. case
#-------------------------------------------------------
# 基本格式
#-------------------------------------------------------
case 值 in
模式1)
    command1
    command2
    ...
    commandN
    ;;
模式2)
    command1
    command2
    ...
    commandN
    ;;
esac
#-------------------------------------------------------
# 例子
#-------------------------------------------------------
echo '输入 1 到 4 之间的数字:'
echo '你输入的数字为:'
read aNum
case $aNum in
    1)  echo '你选择了 1'
    ;;
    2)  echo '你选择了 2'
    ;;
    3)  echo '你选择了 3'
    ;;
    4)  echo '你选择了 4'
    ;;
    *)  echo '你没有输入 1 到 4 之间的数字'
    ;;
esac

3. 循环结构

1. for
#-------------------------------------------------------
# 基本格式
#-------------------------------------------------------
for var in item1 item2 ... itemN
do
    command1
    command2
    ...
    commandN
done
# 写成一行
for var in item1 item2 ... itemN; do command1; command2… done;
#-------------------------------------------------------
# 例子
#-------------------------------------------------------
for loop in 1 2 3 4 5
do
    echo "The value is: $loop"
done


2. while
#-------------------------------------------------------
# 基本格式
#-------------------------------------------------------
while condition
do
    command
done
#-------------------------------------------------------
# 例子
#-------------------------------------------------------
int=1
while(( $int<=5 ))
do
    echo $int
    let "int++"
done
# 无限循环
while :
do
    command
done


3. until
#-------------------------------------------------------
# 基本格式
#-------------------------------------------------------
until condition
do
    command
done
#-------------------------------------------------------
# 例子
#-------------------------------------------------------
a=0
# 一般情况下 while 循环优于 until 循环
until [ ! $a -lt 10 ]
do
   echo $a
   a=`expr $a + 1`
done

4. 循环退出

1. break

2. continue

# 略

四. 编写 Shell 脚本常用指令解析

echo    printf    read    export    readonly    eval    exec

wait    exit    test

1. echo
# 用于向标准输出上打印目标字符串或变量
# 语法格式:
echo [参数] 字符串/变量

2. printf
# 模仿 C 程序库里的 printf(),实现格式化输出
# 语法格式: 
printf [格式控制字符串] [参数]
# 例子
[root@linuxcool ~]# 
printf "%-10s %-8s %-4s\n" 姓名 性别 体重kg
printf "%-10s %-8s %-4.2f\n" 郭靖 男 66.1234  
printf "%-10s %-8s %-4.2f\n" 杨过 男 48.6543  
printf "%-10s %-8s %-4.2f\n" 郭芙 女 47.9876 
[root@linuxcool ~]# 
姓名     性别   体重kg 
郭靖     男      66.12 
杨过     男      48.65 
郭芙     女      47.99 

3. read
# 用于读取单行数据内容,一般用于从标准输入读取数据,然后将值赋给变量
# 语法格式:
read [参数]
# 读取一个输入,并将
read NAME
### 选项
-a	定义一个数组,以空格为间隔符进行赋值
-d	定义一个结束标志
-p	设置提示信息
-n	定义输入文本的长度
-r	禁用转义符(\)
-s	输入字符不在屏幕显示
-t	限定最长等待时间
-u	从文件描述符中读入信息

4. export
# 将变量提升成环境变量,也可以将 Shell 函数输出为环境变量
# 语法格式:
export [参数] [变量]
### 选项
-f	指定函数名称
-n	删除指定的变量
-p	列出所有的环境变量

5. readonly
# 标记 Shell 变量或函数为只读
# 语法格式: 
readonly [参数]
# 显示全部只读变量
readonly
# 显示所有拥有只读属性的数组
readonly -a
### 选项
-a	指向数组
-A	指向关联数组
-p	示全部只读变量
-f	指向函数

6. eval
# 用于重新运算求出参数的内容。当 Shell 程序执行到 eval 语句时,
# Shell 读入参数 args,并将它们组合成一个新的命令,然后执行
# 语法格式: 
eval [参数]

7. exec
# execute,用于调用并执行指定的命令,
# 亦可将前一个命令的输出结果作为后一个命令的标准输入值进行二次处理,功能类似于管道符
# 语法格式:
exec [参数]
# 调用并执行指定命令
exec -c ls

8. wait
# 用于Shell脚本中,等待某个指令执行结束后返回终端,然后才会继续执行后面的指令
# 语法格式:
wait 进程号/作业号
# 等待进程 12345 结束
wait 12345

9. exit
# 直接退出终端

10. test
# 用于检查某个条件是否成立,它可以进行数值、字符和文件三个方面的测试
# test 命令会检测表达式的真假,然后返回数字 0(真)、 1(假)
# 基本格式:
test 测试表达式
数值测试 字符串测试 文件测试
选项 功能 选项 功能 选项 功能
-eq 等于,则为真 = 等于,则为真 -b 文件名 如果文件存在,且为块特殊文件,则为真
-ge 大于等于,则为真 != 不相等,则为真 -c 文件名 如果文件存在且为字符型特殊文件,则为真
-gt 大于,则为真 -z 字符串 字符串长度为零,则为真 -d 文件名 如果文件存在且为目录,则为真
-le 小于等于,则为真 -n 字符串 字符串长度不为零,则为真 -e 文件名 如果文件存在,则为真
-lt 小于,则为真 -f 文件名 如果文件存在且为普通文件,则为真
-ne 不等于,则为真 -r 文件名 如果文件存在且可读,则为真
-s 文件名 如果文件存在且不为空,则为真
-w 文件名 如果文件存在且可写,则为真
-x 文件名 如果文件存在且可执行,则为真
[guyan@DrangonBoat ~]$ a=1
[guyan@DrangonBoat ~]$ b=2
[guyan@DrangonBoat ~]$ test $a -eq $b
[guyan@DrangonBoat ~]$ echo $?
1
# 注意,test 命令通常与 if 等判断语句混合使用
# 如果将 test 语句放在 echo 等输出语句中,将不会产生任何效果

test 命令在 Shell 中非常重要,所以我们可以用另一种格式来书写 test
格式:(中括号与test测试语句之间一定要有空格)
[ test 测试 ]
例子:
[ -w /root/sh_script/backupo.sh ] && echo OK

逻辑操作:’&&’(且)、’||’(或)、’;’(顺序执行) 。逻辑操作允许我们将多条命令语句放在同一行。(略)

五. shell 脚本中的函数

#-------------------------------------------------------
# 函数基本格式
# 1. 可以带function fun() 定义,也可以直接fun() 定义,不带任何参数
# 2. 参数返回,可以显示加:return 返回,如果不加,将以最后一条命令运行结果,
#    作为返回值。 return后跟数值n(0-255)

#-------------------------------------------------------
[ function ] funname [()]
{
    action;
    [return int;]
}
# 例子1:(无参)
demoFun(){
    echo "这是我的第一个 shell 函数!"
}
echo "-----函数开始执行-----"
demoFun
echo "-----函数执行完毕-----"
# 例子2:两数之和(无参)
funWithReturn(){
    echo "这个函数会对输入的两个数字进行相加运算..."
    echo "输入第一个数字: "
    read aNum
    echo "输入第二个数字: "
    read anotherNum
    echo "两个数字分别为 $aNum 和 $anotherNum !"
    return $(($aNum+$anotherNum))
}
funWithReturn
echo "输入的两个数字之和为 $? !"
# 例子3:有参函数
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

六. 提示符

可以指定一个或多个特殊字符作为提示符变量

# 注意,这里的提示符,指的是下面这一串
[userName@hostName pwd]$
[guyan@DrangonBoat ~]$
特殊字符 含义
! 显示该命令的历史记录编号
# 显示当前命令的编号
$ 显示 $ 符作为提示符,如果用户为 root 则显示 #
\ 显示反斜杠
@ 12h 制时间,带 am/pm
\d 日期,格式为 weekday month date
\h 主机名的第一部分
\H 主机名的全称
\n 回车和换行
\s 当前用户使用 Shell 的名字
\t 时间,格式为 hh:mm:ss,24 小时制
\T 时间,格式为 hh:mm:ss,12 小时制
\u 当前用户的用户名
\v Shell 的版本号
\V Shell 的版本号(包括补丁级别)
\W 当前工作目录
#----------------------------------------------------------------
# 使用方式
#   在变量部分提到,环境变量 PS1 和 PS2 是用户的提示符设置
#   我们可以通过修改提示符变量来指定提示符
#----------------------------------------------------------------
# 查看当前提示符
# \u 对应我的用户名,@ 作为分隔符,\W 对应主机名,~ 对应当前工作目录,$ 是用户标识
[guyan@DrangonBoat ~]$ echo $PS1
[\u@\h \W]\$
# 临时修改提示符变量(重启或重新打开 bash 时启用默认格式)
[guyan@DrangonBoat ~]$ PS1="[\t@\v]\$"
[14:20:33@5.1]$
# 永久修改提示符
# 提示符变量是一个环境变量,一般存储在这几个文件中 /etc/profile、~/.bash_profile、~/.bashrc、/etc/bashrc
# 实验机上,PS1 存储在 ~/.bashrc 文件中
# 可以尝试修改该文件,永久更改提示符

六. 调试

调试程序是编程过程中重要的一步,通常使用 bash 命令执行程序的时候添加参数进行初步的测试

bash 选项 Shell 脚本程序文件名

选项 功能
-e 如果一个命令失败就立即退出
-n 读一遍脚本中的命令单步执行。该选项可用于检查脚本中的语法错误
-u 置换时把未设置的变量看作出错
-v 一遍执行脚本,一遍将执行过的脚本命令打印到标准错误输出
-x 提供跟踪执行信息,将执行的每一条命令和结果一次打印出来

或者可以在 Shell 脚本的头部添加选项

#! /bin/bash  -x

七. 自动化

1. 开机自启动

Linux 系统启动大致步骤:主板 BIOS 接电,初始化硬件,UEFI 决定启动的 EFI 程序。Linux 启动 /boot 引导分区,然后读取初始配置文件,初始化系统

现在使用的守护进程大多是 Systemd,取代了以往的 init,最明显的里程碑可能是 CentOS7 中 Systemd 取代了 init 进程

待补充

2. 定时执行任务

参考

原文地址:http://www.cnblogs.com/GuYan-Dragon/p/16853111.html

发表评论

您的电子邮箱地址不会被公开。