Linux Bash语法总结

喜欢而已 提交于 2020-02-04 12:05:46

在学习Linux的过程中,无可避免的会碰到一个既让人喜欢、又令人十分头疼的神奇的东西——bash编程,也就是shell脚本。那么什么是shell脚本呢?shell是一个命令语言解释器,而shell脚本则是Linux命令的集合,按照预设的顺序依次解释执行,来完成特定的、较复杂的系统管理任务,类似于windows中的批处理文件。本篇博文主要介绍bash编程的基础语法讲解。

一、bash编程之变量

1)bash变量类别

本地变量:只对当前shelll进程有效的变量,对其他shell进程无效,包含当前shell进程的子进程。

2)变量赋值:

即向变量的存储空间保存数据,如下

[root@localhost ~]# VAR_NAME=VALUE

3)变量的引用

格式为:${VAR_NAME}

" ":弱引用,里面的变量会被替换;
' ':强引用,里面的所有字符都是字面量,直接输出,所见即所得;

4)环境变量

对当前shell进程及其子shell有效,对其他的shell进程无效!

定义:VAR_NAME=VALUE
导出:export VAR_NAME
撤销变量:unset VAR_NAME
只读变量:readonly VAR_NAME

5)局部变量

在shell脚本中定义,只可以在shell脚本中使用!

6)位置变量

$1,$2...,${10}

7)特殊变量

shell对一些参数做特殊处理,这些参数只能被引用而不能被赋值!

$#:传递到脚本的参数个数
$*:显示所有向脚本传递的参数                  //与位置变量不同,此选项参数可超过9个
$$:获取当前shell的进程号
$!:执行上一个指令的进程号
$?:获取执行的上一个指令的返回值              //0为执行成功,非零为执行失败
$-:显示shell使用的当前选项,与set命令功能相同
$@  与$*相同,但是使用时加引号,并在引号中返回每个参数

8)查看变量

set:查看当前shell进程中的所有变量;
export、printenv、env:查看当前shell进程中的所有环境变量;

9)变量命名规则

1)不能使用程序中的关键字;
2)只能使用数字、字母和下划线,不可使用数字开头;
3)系统变量默认都是大写,自定义变量尽量不要与系统变量冲突;
4)尽量做到见名知意;

10)变量类型

1)数值型:精确数值(整数)、近似数值(浮点数);
2)字符型:char、string;
3)布尔型:true、false;

11)类型转换

1)显示转换;
2)隐式转换;

二、bash的配置文件

功能:设定本地变量、定义命令别名。

1)profile类

profile类:为交互式登录的用户提供配置!

全局:/etc/profile、/etc/profile.d/*.sh
用户:~/.bash_profile

2)bashrc类

bashrc类:为非交互式的用户提供配置!

全局:/etc/bashrc
用户:~/.bashrc

1)bash编程即编写格式及执行方式

1)编写格式

shell脚本第一行必须顶格写,用shabang定义指定的解释器来解释该脚本。

#!/bin/bash       //!即为shebang
//其它的以#开头的行均为注释,会被解释器忽略,可用来注释脚本用途及版本,方便使用管理。

2)执行方式

bash编程属于面向过程编程,执行方式如下:

1)顺序执行:按命令先后顺寻依次执行;
2)选择执行:测试条件,可能会多个测试条件,某条件满足时,则执行对应的分支;
3)循环执行:将同一段代码反复执行多次,因此,循环必须有退出条件;否则,则陷入死循环;

3)bash执行选项

1)bash -n SHELLNAME  #语法测试,测试是否存在语法错误;
2)bash -x SHELLNAME  #模拟单步执行,显示每一步执行过程;

2)bash之算数运算与逻辑运算

1)算数运算

定义整型变量:

1)et VAR_NAME=INTEGER_VALUE            //例如:let a=3
2)declare -i VAR_NAME=INTEGER_VALUE     //例如:declare -i a=3

实现算术运算的方式:

let VAR_NAME=ARITHMATIC_EXPRESSION
VAR_NAME=$[ARITHMATIC_EXRESSION]
VAR_NAME=$((EXPRESSION))
VAR_NAME=$(expr $num1 + $num2)

算法运算符:

+:加法
-:减法
*:乘法
/:整除
%:取余数
**:乘幂

注意:即使没有定义为整型变量,字符型的数字依然可以参与算术运算,bash会执行变量类型的隐式类型转换。

2)逻辑运算

布尔运算:真,假
与运算:真 && 真 = 真
        真 && 假 = 假
        假 && 真 = 假
        假 && 假 = 假
或运算:真 || 真 = 真
  真 || 假 = 真
  假 || 真 = 真
  假 || 假 = 假
非运算:!真=假
        !假=真

3)bash编程之条件测试语句

1)bash条件测试

整型测试:整数比较

例如 [ $num1 -gt $num2 ]

-gt: 大于
-lt: 小于
-ge: 大于等于
-le: 小于等于
-eq: 等于
-ne: 不等于

字符测试:字符串比较

双目:
例如[[ "$str1" > "$str2" ]]

>: 大于则为真
<: 小于则为真
>=:大于等于则为真
<=:小于等于则为真
==:等于则为真
!=:不等于则为真

单目:
-n String: 是否不空,不空则为真,空则为假
-z String: 是否为空,空则为真,不空则假

文件测试:判断文件的存在性及属性等

-a FILE:存在则为真;否则则为假;
-e FILE:存在则为真;否则则为假;
-f FILE: 存在并且为普通文件,则为真;否则为假;
-d FILE: 存在并且为目录文件,则为真;否则为假;
-L/-h FILE: 存在并且为符号链接文件,则为真;否则为假;
-b: 存在并且为块设备,则为真;否则为假;
-c: 存在并且为字符设备,则为真;否则为假
-S: 存在并且为套接字文件,则为真;否则为假
-p: 存在并且为命名管道,则为真;否则为假
-s FILE: 存在并且为非空文件则为值,否则为假;
-r FILE:文件可读为真,否则为假
-w FILE:文件可写为真,否则为假
-x FILE:文件可执行为真,否则为假
file1 -nt file2: file1的mtime新于file2则为真,否则为假;
file1 -ot file2:file1的mtime旧于file2则为真,否则为假;

组合条件测试:在多个条件间实现逻辑运算

与:[ condition1 -a condition2 ]
  condition1 && condition2
或:[ condition1 -o condition2 ]
  condition1 || condition2
非:[ -not condition ]
  ! condition
与:COMMAND1 && COMMAND2
COMMAND1如果为假,则COMMAND2不执行
或:COMMAND1 || COMMAND2
COMMAND1如果为真,则COMMAND2不执行
非:! COMMAND

2)条件测试之if语句

1、if语句之单分支
语句结构:
if 测试条件;then
   选择分支
fi
表示条件测试状态返回值为值,则执行选择分支
例:写一个脚本,接受一个参数,这个参数是用户名;如果此用户不存在,则创建该用户;

#!/bin/bash
if ! id $1 &> /dev/null;then
  useradd $1
fi

2、if语句之双分支
语句结构:
if 测试条件;then
   选择分支1
else
   选择分支2
fi

两个分支仅执行其中之一
例:通过命令行给定一个文件路径,而后判断:如果此文件中存在空白行,则显示其空白行的总数;否则,则显示无空白行;
#!/bin/bash
if grep "^[[:space]]*$" $1 &> /dev/null; then
  echo "$1 has $(grep "^[[:space]]*$" $1 | wc -l) blank lines."
else
  echo "No blank lines"
fi

注意:如果把命令执行成功与否当作条件,则if语句后必须只跟命令本身,而不能引用。

3、if语句之多分支
语句结构:
if 条件1;then
     分支1
elif 条件2;then
     分支2
elif 条件3;then
     分支3
      ...
else
     分支n
fi

例:传递一个用户名给脚本:如果此用户的id号为0,则显示说这是管理员;如果此用户的id号大于等于500,则显示说这是普通用户;否则,则说这是系统用户。
#!/bin/bash
if [ $# -lt 1 ]; then
  echo "Usage: `basename $0` username"
  exit 1
fi
if ! id -u $1 &> /dev/null; then
  echo "Usage: `basename $0` username"
  echo "No this user $1."
  exit 2
fi

if [ $(id -u $1) -eq 0 ]; then
  echo "Admin"
elif [ $(id -u $1) -ge 500 ]; then
  echo "Common user."
else
  echo "System user."
fi

3)bash交互式编程

read [option] “prompt”-p:直接指定一个变量接受参数
-t timaout:指定等待接受参数的时间
-n:表示不换行

例:输入用户名,可返回其shell

#!/bin/bash
read -p "Plz input a username: " userName
if id $userName &> /dev/null; then
    echo "The shell of $userName is `grep "^$userName\>" /etc/passwd | cut -d: -f7`."
else
    echo "No such user. stupid."
fi  

4)条件测试与case语句

case语句:有多个测试条件时,case语句会使得语法结构更明晰
语句结构:

case 变量引用 in
PATTERN1)
  分支1
;;
PATTERN2)
  分支2
;;
...
*)
  分支n
;;
esac

PATTERN:类同于文件名通配机制,但支持使用|表示或者

 a|b:  a或者b*:匹配任意长度的任意字符
?: 匹配任意单个字符
[]: 指定范围内的任意单个字符

例:写一个脚本,完成如下任务,其使用形式如下所示:
script.sh {start|stop|restart|status}

其中:如果参数为空,则显示帮助信息,并退出脚本;
如果参数为start,则创建空文件/var/lock/subsys/script,并显示“starting script successfully.”
如果参数为stop,则删除文件/var/lock/subsys/script,并显示“Stop script successfully.”
如果参数为restart,则删除文件/var/locksubsys/script并重新创建,而后显示“Restarting script successfully.”
如果参数为status,那么:如果文件/var/lock/subsys/script存在,则显示“Script is running…”,否则,则显示“Script is stopped.”

#!/bin/bash
file='/var/lock/subsys/script'
case $1 in
start)
  if [ -f $file ];then
  echo "Script is running..."
    exit 3
  else
  touch $file
  [ $? -eq 0 ] && echo "Starting script successfully."
  fi
  ;;
stop)
  if [ -f $file ];then
  rm -rf $file
  [ $? -eq 0 ] && echo "Stop script successfully."
  else
  echo "Script is stopped..."
  exit 4
  fi
  ;;
restart)
  if [ -f $file ];then
  rm -rf $file
  [ $? -eq 0 ] && echo "Stop script successfully"
  else 
  echo "Script is stopped..."
  exit 5
  fi
  touch $file
  [ $? -eq 0 ] && echo "Starting script successfully"
  ;;
status)
  if [ -f $file ];then
  echo "Script is running..."
  else
  echo "Script is stopped."
  fi
  ;;
*)
  echo "`basename $0` {start|stop|restart|status}"
  exit 2
  ;;
 esac

4)bash编程之循环语句

1)循环之for循环

1、for语句格式一
语句结构:
for 变量名 in 列表; do
    循环体
done
列表:可包含一个或多个元素
循环体:依赖于调用变量来实现其变化
循环可嵌套
退出条件:遍历元素列表结束
例:求100以内所有正整数之和

#!/bin/bash
declare -i sum=0
for i in {1..100}; do
    let sum+=$i
done
echo $sum

2、for语句格式二
for ((初始条件;测试条件;修改表达式)); do
      循环体
done
先用初始条件和测试条件做判断,如果符合测试条件则执行循环体,再修改表达式,否则直接跳出循环。

例:求100以内所有正整数之和(for二实现)
#!/bin/bash
declare -i sum=0
for ((counter=1;$counter <= 100; counter++)); do
  let sum+=$counter
done
echo $sum

2)循环之while语句

while循环语句适用于循环次数未知,或不适用for直接生成较大的列表!

语句结构:
while 测试条件; do
  循环体
done
测试条件为真,进入循环;测试条件为假,退出循环
例1:求100以内所有偶数之和,要求使用取模方法
#!/bin/bash
declare -i counter=1
declare -i sum=0
while [ $counter -le 100 ]; do
  if [ $[$counter%2] -eq 0 ]; then
     let sum+=$counter
  fi
  let counter++
 done
echo $sum

例2:提示用户输入一个用户名,如果用户存在,就显示用户的ID号和shell;否则显示用户不存在;显示完成之后不退出,再次重复前面的操作,直到用户输入q或quit为止
#!/bin/bash
read -p "Plz enter a username: " userName
while [ "$userName" != 'q' -a "$userName" != 'quit' ]; do
  if id $userName &> /dev/null; then
     grep "^$userName\>" /etc/passwd | cut -d: -f3,7
  else
    echo "No such user."
  fi
read -p "Plz enter a username again: " userName
done

while特殊用法:遍历文本文件
语句结构:
while read 变量名; do
  循环体
done < /path/to/somefile
变量名,每循环一次,记忆了文件中一行文本

例:显示ID号为偶数,且ID号同GID的用户的用户名、ID和SHELL
while read line; do
  userID=`echo $line | cut -d: -f3`
  groupID=`echo $line | cut -d: -f4`
  if [ $[$userID%2] -eq 0 -a $userID -eq $groupID ]; then
     echo $line | cut -d: -f1,3,7
  fi
done < /etc/passwd

3)循环之until语句

语句结构:
until 测试条件; do
      循环体
done
测试条件为假,进入循环;测试条件为真,退出循环

例:求100以内所有偶数之和,要求使用取模方法(until实现)

#!/bin/bash
declare -i counter=1
declare -i sum=0
until [ $counter -gt 100 ]; do
  if [ $[$counter%2] -eq 0 ]; then
     let sum+=$counter
  fi
  let counter++
done
echo $sum
例:提示用户输入一个用户名,如果用户存在,就显示用户的ID号和shell;否则显示用户不存在;显示完成之后不退出,再次重复前面的操作,直到用户输入q或quit为止(until实现)

#!/bin/bash
read -p "Plz enter a username: " userName
until [ "$userName" = 'q' -a "$userName" = 'quit' ]; do
  if id $userName &> /dev/null; then
   grep "^$userName\>" /etc/passwd | cut -d: -f3,7
else
echo "No such user."
fi
read -p "Plz enter a username again: " userName
done

4)循环之循环控制和shift

循环控制命令:
1)break:提前退出循环;
2)break [N]: 退出N层循环;N省略时表示退出break语句所在的循环;
3)continue: 提前结束本轮循环,而直接进入下轮循环;
4)continue [N]:提前第N层的循环的本轮循环,而直接进入下轮循环;

5)死循环

#while体while true; do
      循环体
done
#until体
until false; do
      循环体
done

例1:写一个脚本,判断给定的用户是否登录了当前系统
(1) 如果登录了,则脚本终止;
(2) 每5秒种,查看一次用户是否登录;
#!/bin/bash
while true; do
    who | grep "gentoo" &> /dev/null
    if [ $? -eq 0 ];then
break
    fi
    sleep 5
done
echo "gentoo is logged."

6)shift

位置参数可以用shift命令左移,比如shift 3 表示原来的$4现在变成$1,原来的$5变成$2等等,原来的$1、$2、$3丢弃,$0不移动。不带参数的shift命令相当于shift 1。

我们知道,对于位置变量或命令行参数,其个数必须是确定的,或者当shell程序不知道其个数时,可以把所有参数一起复制给“$*”。若用户要求 Shell 在不知道位置变量个数的情况下,还能逐个的把参数一一处理,也就是在 $1 后为 $2,在 $2 后面为 $3 等。在 shift 命令执行前变量 $1 的值在 shift 命令执行后就不可用了。

实例一如下:

[root@localhost ~]# cat 1.sh
#!/bin/bash
while [ $# -ne 0 ]
do
echo "第一个参数为:$1  参数个数为:$#"
shift
done
[root@localhost ~]# sh 1.sh 1 2 3 4
第一个参数为:1  参数个数为:4
第一个参数为:2  参数个数为:3
第一个参数为:3  参数个数为:2
第一个参数为:4  参数个数为:1

从上面例子中可以看出shift命令每执行一次,变量的个数($#)减1,而变量的值提前一位。

实例二如下:

[root@localhost ~]# cat 2.sh
#!/bin/bash
if [ $# -eq 0 ]
then
echo "Usage:2.sh 参数"
exit 1
fi
sum=0
while [ $# -ne 0 ]
do
sum=`expr ${sum} + $1`
shift
done
echo "sum is:${sum}"
[root@localhost ~]# sh 2.sh 10 20 30
sum is:60

shift命令还有一个重要用途,Bash定义了9个位置变量,从$1到$9,这并不意味这用户在命令行只能使用9个参数,借助shift命令可以访问多于9个的参数。

shift命令一次移动到参数的个数由其所带的参数指定,例如当shell程序处理完前9个命令行参数后,可以使用shift 9命令把$10移动到$1。

5)bash编程之函数

语法结构:

function F_NAME {    
   函数体
  }
或
  F_NAME() {
    函数体
  }
可调用:使用函数名,函数名出现的地方,会被自动替换为函数;
函数的返回值:
函数的执行结果返回值:代码的输出
函数中使用打印语句:echo, printf
函数中调用的系统命令执行后返回的结果
执行状态返回值:
默认取决于函数体执行的最后一个命令状态结果
自定义退出状态码:return [0-255]

注意:函数体运行时,一旦遇到return语句,函数即返回!

1)函数可以接收参数

在函数中调用函数参数的方式同脚本中调用脚本参数的方式:

位置参数
$1, $2, …
$#, $*, $@

实例:
要求如下:
1)提示用户输入一个可执行命令;
2)获取这个命令所依赖的所有库文件(使用ldd命令);
3)复制命令之/mnt/sysroot目录;
4)复制各库文件至/mnt/sysroot对应的目录中;

[root@localhost ~]# cat 1.sh
#!/bin/bash
target=/mnt/sysroot/
[ -d $target ] || mkdir $target
preCommand() {
    if which $1 &> /dev/null; then
  commandPath=`which --skip-alias $1`
  return 0
    else
  echo "No such command."
  return 1
    fi
}

commandCopy() {
    commandDir=`dirname $1`
    [ -d ${target}${commandDir} ] || mkdir -p ${target}${commandDir}
    [ -f ${target}${commandPath} ] || cp $1 ${target}${commandDir}
}

libCopy() {
    for lib in `ldd $1 | egrep -o "/[^[:space:]]+"`; do
libDir=`dirname $lib`
[ -d ${target}${libDir} ] || mkdir -p ${target}${libDir}
[ -f ${target}${lib} ] || cp $lib ${target}${libDir}
    done
} 
read -p "Plz enter a command: " command

until [ "$command" == 'quit' ]; do

  if preCommand $command &> /dev/null; then
    commandCopy $commandPath
    libCopy $commandPath
  fi
    exit 1
done
[root@localhost ~]# sh 1.sh
Plz enter a command: cat
[root@localhost ~]# ls /mnt/sysroot/bin/
cat
[root@localhost ~]# ls /mnt/sysroot/
bin  lib64

2)bash编程之信号捕捉

trap命令用于在shell程序中捕捉到信号,之后可以由三种反应方式:
1)执行一段程序来处理这一信号;
2)接收信号的默认操作;
3)忽略这一信号;

示例:
写一个脚本,能够ping探测指定网络内的所有主机是否在线,当没有执行完时可接收ctrl+c命令退出。

[root@localhost ~]# cat 1.sh
#!/bin/bash
quitScript() {
   echo "Quit..."
}    
trap 'quitScript; exit 5' SIGINT

cnetPing() {
  for i in {1..254}; do
    if ping -c 1 -W 1 $1.$i &> /dev/null; then
      echo "$1.$i is up."
     else
      echo "$1.$i is down."
    fi
    done
}

bnetPing() {
  for j in {0..255}; do
    cnetPing $1.$j 
  done
}
anetPing() {
for m in {0..255}; do
bnetPing $1.$m
done
}

netType=`echo $1 | cut -d"." -f1`

if [ $netType -ge 1 -a $netType -le 126 ]; then
anetPing $netType
elif [ $netType -ge 128 -a $netType -le 191 ]; then
bnetPing $(echo $1 | cut -d'.' -f1,2)
elif [ $netType -ge 192 -a $netType -le 223 ]; then
cnetPing $(echo $1 | cut -d'.' -f1-3)
else
echo "Wrong"
exit 2
fi
[root@localhost ~]# sh 1.sh 192.168.1.1
192.168.1.1 is down.
192.168.1.2 is down.
192.168.1.3 is down.
192.168.1.4 is down.
192.168.1.5 is down.
192.168.1.6 is down.
192.168.1.7 is down.
192.168.1.8 is down.
192.168.1.9 is down.
192.168.1.10 is up.
192.168.1.11 is down.
^CQuit...

——————————本次博文到此结束,感谢阅读——————————

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!