> 文档中心 > 【Linux】shell语法入门手册 语法大全

【Linux】shell语法入门手册 语法大全

shell学习笔记 yxc的linux

shell语法目录

  • 概论
      • 运行方式
          • 直接用解释器执行
          • 作为可执行文件运行
  • 注释
      • 单行注释
      • 多行注释
  • 变量
      • 定义变量
      • 引用变量
      • 只读变量
      • 删除变量
      • 变量类型
      • 字符串
  • 默认变量
      • 文件参数变量
      • 其他参数相关变量
  • 数组
      • 定义
      • 调用数组元素中的值
      • 数组长度
  • expr命令
    • 重要说明
      • 字符串表达式
      • 整数表达式
      • 逻辑关系表达式
  • read命令
  • echo命令
      • 显示普通字符串
      • 显示转义字符
      • 显示变量
      • 显示换行
      • 显示不换行
      • 显示结果定向至文件
      • 原样输出
      • 显示命令执行结果
  • printf命令
  • test命令与判断符号[]
      • 逻辑运算符
      • test
          • 文件类型判断
          • 文件权限判断
          • 整数间的比较
          • 字符串比较
          • 多重条件判定
      • 判断符号[]
  • 判断语句
      • 单层if
      • 单层if else
      • if-elif-elif-else
      • case
  • 循环语句
      • for-in-do-done
      • for((...;...;...))do...done
      • while...do...done循环
      • until...do...done循环
      • break命令
      • continue命令
      • 死循环处理
  • 函数
      • 调用函数
      • 函数的输入参数
      • 函数内的局部变量
  • exit命令
  • 文件重定向
      • 重定向命令
  • 引入外部脚本
  • 附录

概论

shell是我们通过命令行与操作系统沟通的语言。它是一个解释性语言,不需要编译运行。
shell脚本可以直接在命令行中执行,也可以将一套逻辑组织成一个文件,方便复用。
而我们使用的终端,可以将其想象成一个大的文件,在逐行执行命令。
Linux常见shell脚本有很多种,一般默认使用bash。文件开头指明脚本解释器 #! /bin/bash

运行方式

新建一个文件,bash文件后缀是.sh

#! /bin/bashecho "Hello World!"
直接用解释器执行
root@kali:~$ bash filename.shHello World! # 脚本输出
作为可执行文件运行

首先要赋予文件权限

root@kali:~$ chmod +x filename.sh #使脚本具有可执行权限x

接下来有三种执行方式,一般第一种最常用

root@kali:~$ ./filename.sh #当前路径下执行Hello World! # 脚本输出
root@kali:~$ /home/root/filename.sh #绝对路径下执行Hello World! # 脚本输出
root@kali:~$ ~/filename.sh # 家目录下执行Hello World! # 脚本输出

关于文件权限的问题——跳转至附录


注释

单行注释

#后均为注释

#注释echo 'Hello World' #注释

多行注释

:<<EOF注释1注释2注释3EOF

其中EOF可以替换为任意字符串


变量

定义变量

定义字符串,以下三种都可以

name='Genevieve_xiao'name="Genevieve_xiao"name=Genevieve_xiao

引用变量

加上$${}表示引用变量,{}可省略,但是遇到变量边界歧义时最好加上。

name=Genevieve_xiaoecho $nameecho ${name}hahaha

只读变量

readonly或者declare再加变量

name=Genevieve_xiaoreaonly namedeclare -r  namename=hahaha #报错

删除变量

unset

name=Genevieve_xiaounset nameecho $name #输出空行

变量类型

分为自定义变量(局部)和环境变量(全局)。
子进程不能访问自定义变量。

name=Genevieve_xiao #自定义变量export name #method1declare -x name #method2局部变全局
export name=Genevieve_xiao #定义环境变量declare +x name #全局变局部

字符串

单双引号的区别:

  • 单引号内容会原样输出,不会执行、不取变量、不会转义
  • 双引号内容可以执行、可以取变量、可以转义
    不加引号同双引号效果。

获取字符长度

name=Genevieve_xiaoecho ${#name}

提取子串

nam=Genevieve_xiaoecho ${name:0:5}

默认变量

文件参数变量

在执行shell时向脚本传递的参数。
$0是./文件名
$1第一个参数
$2第二个参数

#! /bin/bashecho $0echo $!echo $2
root@kali:~$ chmod +x filename.shroot@kali:~$ ./filename.sh 1 2./filename.sh #脚本输出$01 #脚本输出$12 #脚本输出$2

其他参数相关变量

  • $#文件传入的参数个数
  • $*所有参数构成的由空格隔开的字符串"$1 $2"
  • $@每个参数分别由双引号括起来的字符串"$1" "$2"
  • $$脚本当前运行的进程ID
  • $?上一条命令的退出状态exit code。0表示正常退出,其他值表示错误
  • $(commmand)返回这条指令的stdout 可嵌套
  • 'command'返回这条指令的stdout 不可嵌套

数组

可以存放不同类型的值,只支持一维数组,初始化不用指明数组大小,下标从零开始。

定义

array=(1 abs "hahaha" bala)

或者

array[0]=1array[1]=absarray[2]="hahaha"array[3]=bala

调用数组元素中的值

调用单个元素

${array[index]}

调用整个数组

${array[@]}${array[*]}

数组长度

${#array[@]}${#array[*]}

expr命令

用于求表达式的值 expr 表达式

重要说明

  • 用空格隔开每一项
  • 用反斜杠放在shell特定的字符前面(转义)
  • 对包含空格和其他特殊字符的字符串要用单引号括起来
  • expr会在stdout中输出结果,不需要嵌套echo。如果为逻辑关系表达式,若结果为真,stdout为1,否则为0。
  • expr的exit code:如果为逻辑关系表达式,若结果为真,exit code为0,否则为1。
  • 在这里出现的string 下标从1开始
  • 优先级:字符串表达式>算术表达式>逻辑关系表达式

字符串表达式

  • lengrh STRING返回string的长度
  • index STRING CHARSET任意cherset中的单个字符在string最先出现的位置 下标从1开始 如果不存在返回0
  • substr STRING POSITION LENGTH返回string从position开始长度为length的子串 下标从1开始。若pos或len不合法则返回空字符串
str="Hello World!"expr length "$str" #输出12expr index "$str" abcde #输出2expr substr "$str" 2 3 #输出ell

整数表达式

  • + -
  • * / %
  • ()

注意*()需要转义

expr $a + $bexpr $a - $bexpr $a \* $bexpr $a / $bexpr $a % $bexpr \( $a + 1 \) \* \( $b + 1 \)

逻辑关系表达式

  • |如果第一个参数非空且非0,则返回第一个参数的值且忽略第二个参数,否则返回第二个参数的值,但要求第二个参数的值也是非空或非0,否则返回0。
  • &如果两个参数都非空且非0,则返回第一个参数,否则返回0。若第一个参数为0,则直接忽略第二个参数并返回0。
  • < = >如果为true则返回1,否则返回0。这里==与=等价
  • ()
a=3b=0expr $a \> $b #1expr $a '>' $b #1expr $a \& $b #0expr $a \| $b #3

read命令

标准输入中读取单行数据。当读到文件结束符时,exit code为1,即返回假,否则为0 ,循环语句中会用到
参数

  • -p接提示信息
  • -t跟秒数,超过等待时间则自动忽略此条命令
read -p "input u name:" -t 30 name

echo命令

用于输出字符串echo SRING

显示普通字符串

echo "Hello World"echo Hello World

显示转义字符

echo "\"Hello World\""echo \"Hello World\"

显示变量

name=Genevieve_xiaoecho "My name is $name"

显示换行

-e开启转义

echo -e "hi\nhahaha" 

输出

hihahaha

显示不换行

\c不换行

echo -e "hi \c"echo "hahaha"

输出

hi hahaha

显示结果定向至文件

echo "Hello World" > output.txt

相当于直接创建了一个新的文件output.txt并将输出结果放进去

原样输出

单引号

echo '\"$name"'

输出

\"name"

显示命令执行结果

注意是反引号` 而不是单引号 '.

echo `expr 3 + 4`

输出

7

printf命令

用于格式化输出,类似于c中的printf函数
默认不添加换行符
printf format-string [arguments...]
举例

printf "%d * %d =%d\n" 5 6 `expr 5 \* 6`

就相当于是printf函数去掉了括号和逗号分隔符,shell里的分隔符是空格


test命令与判断符号[]

逻辑运算符

  • &&与,||
  • 用于连接两个expr,而单个的& |则是在expr里面进行逻辑关系运算
  • 短路原则
  • exit code 为0,表示真,非0,表示假

test

test命令用exit code返回结果,不同于expr用stdout返回
test用于判断文件类型,以及对变量做比较。

一个很妙的运算

test -e filename.sh && echo "exist" || echo "not exist"

如果文件存在,则第一项为真,执行&&后的那一项,也就是第二项,输出exist,这样||前面为真,则直接忽略第三项;
如果文件不存在,则第一项为假,自动忽略第二项,执行第三项, 输出not exist。

文件类型判断
  • -e是否存在
  • -f是否为文件
  • -d是否为目录
文件权限判断

格式test -r fliename

  • -r是否可读
  • -w是否可写
  • -x是否可执行
  • -s是否为非空文件
整数间的比较

格式test $a -eq $b

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

这几个是用于数值间的比较,而符号是用于字符串间的比较。

字符串比较
  • -z是否为空
  • -n是否非空
  • ==是否等于
  • !=是否不等于
test -z $strtest -n $strtest str1==str2test str1!=str2
多重条件判定

格式test -r filename -a -x filename

  • -aall两个条件是否同时成立
  • -oor两个条件是是否至少一个成立
  • !取反 例如!-x

判断符号[]

与test用法几乎一样,更常用与if语句中,[[]]是[]的加强版。
不过本质上[是个命令,]是个标志。

[ -e filename.sh ] && echo "exist" || "not exist" 

注意

  • 每一项用空格隔开
  • 变量最好用双引号引起来
  • 常数最好用单引号或双引号引起来

如果不引起来的话,万一变量里有空格,就有可能会报错。

一般来说,比起使用expr和test,在进行逻辑关系表达时,更多地还是使用[]


判断语句

所有的if或者elif后面都有then

单层if

格式

if conditionthen语句1语句2..fi

单层if else

if conditionthen语句1语句2...else语句1语句2...fi

if-elif-elif-else

if conditionthen语句1语句2...elif conditionthen语句1语句2...elif conditionthen语句1语句2...else语句1语句2...fi

case

类似于c的switch

case $变量 in值1)语句1语句2...;;值2)语句1语句2...;;*)语句1语句2...;;esac

循环语句

for-in-do-done

for var in val1 val2 val3do语句1语句2...done

举例
依次输出参数

for i in a 9 ssdo echo $idone

依次输出1-10

for i in $(seq 1 10) #seq序列doecho $idone

依次输出a-z

for i in {a..z} #可以数字 可以倒序doecho $idone

依次输出当前目录的文件名

for file in `ls`doecho $filedone

for((…;…;…))do…done

类似于c中的for

for ((expression; condition; expression))do 语句1语句2done

这里的expression不需要转义

for ((i=1 ;i<=10 :i++))doecho $idone

while…do…done循环

while conditiondo语句1语句2...done

文件结束符为ctrl+D,输入结束符后read指令返回false,循环停止。

until…do…done循环

当条件为真时结束。

until conditiondo 语句1 语句2 ..done

break命令

跳出当前一层循环,但与c不同的是,如果break出现在case里的话,break不是跳出case语句,而是忽略case,跳出case外的一个循环语句。

while read namedo    for ((i=1;i<=10;i++))    do case $i in     8)  break  ;;     *)  echo $i  ;; esac    donedone

该实例里每次输入非EOF的字符串,会输出一遍1-7。
在for语句读到8的时候,break掉了for语句,进入下一次while。

continue命令

同c里的continue,跳过当前这次循环。

死循环处理

两种方法

  • ctrl+c
  • top命令找到进程PID,输入kill -9 PID

函数

跟c里的函数基本一样,但是return值不同,是exitcode ,只能是0-255之间的数,0表示正常结束。
若要调用函数的输出结果,可通过echo输出到stdout中再通过$(function_name)来获取
return值通过$?获取。
调用函数的时候不需要写()。
格式

[function] func_name(){ #function 可不写语句1语句2...}

调用函数

func(){name=Gxecho $name}func

输出

Gx

若要获取return和stdout

func(){name=Gxecho $nanereturn 111}output=${func} #or`func`ret=$?echo "output=$output"echo "return=$ret"

输出

ouput=Gxreturn=111

函数的输入参数

$1表示第一个输入参数,$2表述第二个输入参数
$0仍然是文件名。

func() {  # 递归计算 $1 + ($1 - 1) + ($1 - 2) + ... + 0    word=""    while [ "${word}" != 'y' ] && [ "${word}" != 'n' ]    do read -p "要进入func($1)函数吗?请输入y/n:" word    done    if [ "$word" == 'n' ]    then echo 0 return 0    fi      if [ $1 -le 0 ]     then echo 0 return 0    fi      sum=$(func $(expr $1 - 1))    echo $(expr $sum + $1)}echo $(func 10) #输出55

注:为什么只输出了55而没有输出其他echo的值呢,只要在echo后面有$(),它就会截获stdout里的值。递归往回调用的时候上一层的echo会被这一层的sum=$()截获而没有输出,而最后一层的echo被函数外的$捕获,函数外的echo输出55.

函数内的局部变量

变量未经声明都是全局,除非特殊命名。
格式local 变量名=变量值


exit命令

exit命令用来退出当前shell进程,并返回一个退出状态;使用$?可以接收这个退出状态。
exit命令可以接受一个整数值作为参数,代表退出状态。如果不指定,默认状态值是 0。
exit退出状态只能是一个介于 0~255 之间的整数,其中只有 0 表示成功,其它值都表示失败。
exit n返回n并退出进程


文件重定向

每个进程默认打开3个文件描述符:

  • stdin标准输入,从命令行读取数据,文件描述符为0
  • stdout标准输出,向命令行输出数据,文件描述符为1
  • stderr标准错误输出,向命令行输出数据,文件描述符为2
    可以用文件重定向将这三个文件重定向到其他文件中。

重定向命令

  • command > file 将stdout重定向到file中
  • command < file 将stdin重定向到file中
  • command >> file 将stdout以追加方式重定向到file中
  • command n> file 将文件描述符n重定向到file中
  • command n>> file 将文件描述符n以追加方式重定向到file中

stdout和stdin可以同时重定向

test.sh<input.txt>output.txttest.sh<output.txt>input.txt #这两个是等价的

引入外部脚本

类似于c的include,引入其他文件中的代码
两种格式

. filename
source filename

filename可以写绝对路径
相当于把这个外部脚本在当前文件中展开

举例
创建test1.sh

#! /bin/bashname=Gx

创建test2.sh

#! /bin/bashsource test1,txtecho My name is :$name

输出

My name is Gx

附录

关于文件权限的问题
如何查看文件权限呢

root@kali:~$ ls -l filename.sh
-rw-rw-r--      ... (省略)...  # 脚本输出

脚本输出这里的123位rw-是作者user权限,456位rw-是同用户组group权限,789位r--是其他用户other权限,而第0位若是-则表示普通文件file,若是d则表示目录directory。
r指read 可读权限(4),w指write 可写入权限(2),x指execute 可执行权限(1)。

顺便简单介绍一下chomd的用法

字母法公式 chomd [ u g o a ] [ + - = ] [ r w x ] filename
数字法公式 chomd [ num1 ][ num2 ][ num3 ] filename

u-user,g-group,o-other,a-all
r-read-4,w-write-2,x-execute-1
num1-user的权限和,num2-group的权限和,num3-other的权限和。
+增加权限,-撤销权限,=赋予权限。
不写ugoa默认就是all。

举例1 chomd u+rwx, g+rw, o+rw filename.sh 等价于 chomd 755 filename.sh