Shell编程的究极系列(基本语法)

恩,这是由奇技淫巧组成的语言,没有之一。奇技淫巧到,他的语法出如今你无数个灵光一现时刻~python

我:蒋哥你shell好厉害!mysql

蒋哥:恩git

我:要多久啊sql

蒋哥:(基于Java,python,js等)要一段时间docker

1、理解shell、变量、环境变量

shell,壳,计算机命令终端,每一个unix体系的机器最原始都是使用这个与计算机操做系统进行交互的,相似的,windows操做系统下满就是bat(批处理文件)。每次Linux系统启动,登录以后,都会默认启动几个这种命令终端,若是图形界面有的话,通常会默认进入图形界面。图形界面之上,咱们也可使用Terminal这种模拟的命令终端,进行命令操做,这东西相似于windows的cmd。shell

一、变量

对于编程这么多年,变量很好理解,无非就是一个瞎几把命名的一个单词,赋值一个value嘛~真的吗?奇技淫巧来了,请看下面数据库

# ①
var1=123
# ②
var2='jicheng${var1}'
# ③
var3="jicheng${var1}"
# ④
var4=`pwd`
# ⑤
var4 = `pwd`
# ⑥
var4=$(pwd)

①shell变量没类型,弱类型,万物皆字符串,即便是这个,也是var1值是"123"express

②③单双引号不同,单引号内部不转义,写啥是啥;双引号会转义,会替换变量值编程

④这种上引号是执行引用起来的字符串,因此被引用的要能被执行,结果赋值给等号左面的变量ubuntu

⑤这种是错误语法。赋值操做的等号两边不能有任何空格,这种有空格的,后面会将

⑥和④同样

完了?远远还没,来点晋级的:

# ①
var4=${var1:-value}
var4=${var1-value}
# ②
var5=${var1:=value}
var5=${var1=value}
# ③
var6=${var1:?value}
var6=${var1?value}
# ④
var7=${var1:+value}
var7=${var1+value}

解说:不带冒号表明var1是否为未赋值,带了表明var1是否为未赋值或是空值

①var1未赋值(或空值)将"value"赋值给var4变量

②var1未赋值(或空值)将"value"赋值给var4,且一样赋值给var1

③var1未赋值(或空值)将"value"做为标准错误输出,用于进行变量判空用的

④var1未赋值(或空值),什么都不作,不然value代替var1的值,赋值给var7,var1的值不变

二、shell脚本内部变量

一个真正的shell脚本在执行过程当中,会有一些特殊的变量,这个对理解shell程序相当重要,来战:

#!/bin/bash

# shell_test.sh脚本


echo $0
echo $(basename $0)
echo $1 $2
echo $#
echo $*
echo "$*"
echo $@
echo "$@"
  1. $0:

    获取执行脚本本身的文件名,执行的输入全字符串,例如./shell.sh、/etc/profile_test.sh,若是想单纯获取名字,要用:$(basename $0)

  2. $1,$2,$3……..$n这种

    回去执行名称后面对应的输入参数,$1表明第一个,$n表明第n个

  3. $#

    获取输入参数一共有多少个

  4. $*与$@

    不加引号,两个同样:都是将入参使用IFS定义的分隔符进行拆分;加了就有区别:$*会把全部入参当作一个总体的字符串,而$@会将使用双引号的入参当作一个,不进行IFS分隔符拆分操做。下面代码是两个变量的对比试验:

#!/bin/bash

# 对$*与$@作对比试验

# 将带引号的入参进行IFS分割拆分
for i in $*;
do
	echo $i
done


# 恩,这就是一个大的字符串
for i in "$*";
do
	echo $i
done


# 将带引号的入参进行IFS分割拆分
for i in $@;
do
	echo $i
done

# 不将带引号的入参进行IFS分割拆分
for i in "$@";
do
	echo $i
done

三、系统+环境变量

每次运行shell脚本,或者咱们开启一个Terminal,或者咱们远程登录一个命令终端,其实都是有预先设置好的变量存在的,这个就叫:环境变量。若是是全局的环境变量,就是说,每一个用户登陆都能看到的,能够说是全局变量,相似于windows里面的系统变量的概念;若是环境变量只是当前用户可以看到的,那就是局部环境变量,相似于windows里面的环境变量的概念。内部有几个很关键的脚本文件,专门用来设置全局与局部变量的,大体总结下:

a、全局环境变量设置(系统变量)

  1. /etc/profile:最先启动,在系统登陆的时候就进行调用,其后都不会调用的设置环境变量的脚本。其中会调用执行下面的下面的每个文件
  2. /etc/profile.d/*.sh:被1文件调用的各个文件,若是要设置全局的系统环境变量,最好分类别分别写个.sh可执行文件放入这个文件夹,系统启动之时会被调用执行,设置环境变量

下面是profile的源代码:

# /etc/profile: system-wide .profile file for the Bourne shell (sh(1))
# and Bourne compatible shells (bash(1), ksh(1), ash(1), ...).

if [ "`id -u`" -eq 0 ]; then
# id -u 显示登录用户id,0是root用户
  PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
else
  PATH="/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games"
fi
export PATH

# 下面的判断语法是否是很熟悉呢?
# 这里主要进行PS1这个环境变量的设置,主要影响交互式命令号行头标示符
if [ "${PS1-}" ]; then
  if [ "${BASH-}" ] && [ "$BASH" != "/bin/sh" ]; then
    if [ -f /etc/bash.bashrc ]; then
    # 到这里就是判断当前是否是不是使用sh且存在bash.bashrc文件
      . /etc/bash.bashrc
    fi
  else
  	# sh模式(不是bash模式),就是所谓的POSIX兼容模式
    if [ "`id -u`" -eq 0 ]; then
      PS1='# '
    else
      PS1='$ '
    fi
  fi
fi

if [ -d /etc/profile.d ]; then
# 第一个判断文件夹是否存在
  for i in /etc/profile.d/*.sh; do
  # 遍历文件夹中的每一个文件
    if [ -r $i ]; then
    # 判断单个文件是否存在,执行
      . $i
    fi
  done
  unset i
fi

b、局部用户环境变量设置

下面针对用户环境变量设置是按顺序,找到的就会执行,没找到就不执行:

  1. $HOME/.bash_profile:有可能存在,针对我的用户设置用户级别的环境变量
  2. $HOME/.bash_login:有可能存在,针对我的用户设置用户级别的环境变
  3. $HOME/.profile:有可能存在,针对我的用户设置用户级别的环境变

通常状况下,会只存在一个。例如,Debian系统下面,用户目录下面只有.profile。但是内部又调用执行类另一个同目录的文件:.bashrc。下面是真是的源码:

# ~/.profile: executed by Bourne-compatible login shells.

if [ "$BASH" ]; then
# 若是BASH变量存在就执行这个代码块
  if [ -f ~/.bashrc ]; then
  # 若是.bashrc这个文件存在在用户目录中,就执行这个代码块
    . ~/.bashrc
    # 这种的另外一种写法是:source ~/.bashrc
  fi
fi

mesg n || true
# ~/.bashrc: executed by bash(1) for non-login shells.

# 这里都注释掉了,认真阅读可见,咱们平时经常使用的命令简写都出自这里,只不过这个系统定制性的给去掉了

# Note: PS1 and umask are already set in /etc/profile. You should not
# need this unless you want different defaults for root.
# PS1='${debian_chroot:+($debian_chroot)}\h:\w\$ '
# umask 022

# You may uncomment the following lines if you want `ls' to be colorized:
# export LS_OPTIONS='--color=auto'
# eval "`dircolors`"
# alias ls='ls $LS_OPTIONS'
# alias ll='ls $LS_OPTIONS -l'
# alias l='ls $LS_OPTIONS -lA'
#
# Some more alias to avoid making mistakes:
# alias rm='rm -i'
# alias cp='cp -i'
# alias mv='mv -i'

2、字符串与数字运算

对于平常的使用,最多见的莫过于字符串拼接,与数字的运算,下面分别来讲说这两方面

一、字符串拼接

#!/bin/bash

str1=hello
str2=shell
str3=world
echo $str1$'\t'$str2$'\t'$str3
# 输出为:hello	shell	world
  • shell中没有所谓的“+”链接符,直接用啥放啥,直接放!
  • 中间不能有空格,不然字符串会被分割拆分
  • 拆分字符串的环境变量是IFS,默认值为:IFS=$' \t\n'
  • 奇技淫巧又来了:上面介绍过单引号里面不转义,而这里又转义了呢?f**c!关键点在于这里的单引号前面多了个$。下面是bash官方文档针对这一段的解释,我翻译了一下
$'string'这种类型的值会被特殊解析。这种值,内部使用反斜杠加一个字符,都会按照标准c的模式进行转义。若是存在这种反斜杠转义序列, 映射关系以下:
       /a     警告(铃声)
       /b     退格
       /e     一个转义字符
       /f     换页
       /n     换行
       /r     回车
       /t     水平制表符
       /v     垂直制表符
       //     反斜杠
       /'     单引号
       /nnn   八进制
       /xHH   十六进制
       /cx    控制x字符(没弄明白这个何时用)

二、算术运算

shell自己就是万物皆字符串,因此对于纯数学计算支持的不是很好。大致上分两大类:整数运算与浮点数运算。实践的方式有三种,前两种是整数运算,最后一种是浮点数运算

a、使用expr

#!/bin/bash 
# An example of using the expr command 
var1=10
var2=20
var3=$(expr $var2 / $var1)
echo The result1 is $var3
var3=$(expr $var2 \* $var1)
echo The result2 is $var3

这东西很差使用,并且是很是。其中还会支持以下的一些操做:

  • ARG1 | ARG2:两个参数谁不为空(被赋了值),结果就是谁,都不为空,结果为ARG1,不然结果为0
  • ARG1 & ARG2:两个参数都不为空(被赋值了),结果为ARG1,不然为0
  • ARG1 < ARG2
  • ARG1 <= ARG2
  • ARG1 = ARG2
  • ARG1 != ARG2
  • ARG1 >= ARG2
  • ARG1 > ARG2

这东西,若是是取结果值,赋值给其余变量的话,通过实际的测验,还有一些猫腻,请仔细看下面代码与注释:

#!/bin/bash

# 测试expr取值操做

expr $var \| 3 #若是是没被赋值参与了运算的,这时候会报错
$var=
expr $var \| 3 #这种被赋了个空值,也会报错
$var=1
expr $var \| 3 #这种才不会报错,按照正常逻辑返回值:1
$var=
expr "$var" \| 3 #这种也不会报错,直接取了var的空值,结果为:3

expr 3 | 2 #不转义“或”符号报错,shell会当作管道符号处理,而2并非命令

expr 4\*3 #这种直接返回结果:4*3(字符串)
expr 4\* 3 #这种语法错误,操做符的两边必需要有空格

可见shell下面的算术运算操做并不容易,很是多的小细节,恩就是所谓的“奇技淫巧”!

b、使用中括号:$[ARG1*ARG2]

中括号主要是为了取代expr而出现的,主要也是用于整数的算术运算与逻辑运算,而且可以方便的取值操做。毕竟,使用expr有太多奇技淫巧,太多要注意的语法细节了,而中括号,偏偏能很好的解决这些。下面是一些语法举例:

#!/bin/bash

# 中括号算术运算举例

[4*4] #单独这样会有语法错误,由于中括号语法要配合$来使用,报错信息:[4*4]:未找到命令
[ 4*4 ] #这种其实没有语法错误,中括号里面两边有了空格这种,属于判断语句,下面会介绍到
[4 * 4] #这种操做符有了空格,会被命令解释器拆分红三个命令:[四、*、4],而[4并非命令,因此报错
$[4*6] #这种表达式自己是没问题的,可以求结果,但是结果求出来了shell会运行结果,显然24不是命令,报错


var=$[3 *4] #这种是最正确的,操做符左右不用担忧空格、也不用担忧转义问题,求结果,并赋值操做

仔细阅读上面注释,就能够了。是否是感受shell很是死扣一些细节?这个脚本语言就这个德行!

c、浮点数解决方案:bc

恩,最后了,一步步闯关以后,这东西是“公主”!最好控制的东西(就是奇技淫巧少)。好控制不表明好用,来看看bc两种模式

①交互模式

bash计算器其实是一种编程语言,它容许在命令行中输入浮点表达式,而后解释并计算该 表达式,最后返回结果。bash计算器可以识别:

  • 数字(整数和浮点数)
  • 变量(简单变量和数组)
  • 注释(以#或C语言中的/* */开始的行)
  • 表达式
  • 编程语句(例如if-then语句)
  • 函数

下面是我一顿瞎几把搞的代码:

jicheng:~/Project/shell_test$ bc -q #q是不显示软件介绍

34.5
23*34.645645
796.849835
234*3-(3/ 234)#支持混合运算
702
3/ 234
0
3.0/ 234.023
0
scale= 6 #默认是0,不设置,小于1的除法结果显示0
3.0/ 234.023
.012819
3/ 234
.012820
var1=10 #内部支持变量
var1 * 4
40
var2 = var1 / 5
print var2
2
quit 
jicheng:~/Project/shell_test$
②脚本中使用bc

基本语法为:variable=$(echo "options; expression" | bc)

#!/bin/bash
var1=100
var2=45
var3=$(echo "scale=4; $var1 / $var2" | bc) # 看到如何设置精确值
echo The answer for this is $var3

3、输入输出与重定向

这两个话题也是一大难。哎~难点在于不少不少的细节,仍是那样,难在正所谓的"奇技淫巧",咱们分开2小节来说:

  1. 输入
  2. 输出与重定向

一、输入

对于一个计算机语言来讲,输入,无非就是:用户交互式输入与文件内容的输入。Shell里面也同样。针对用户的输入,能够从命令传入,也可实时读取用户的外设输入。而针对这些个输入,shell会有相应的读取与保存方式,下面分开来介绍

a、命令传参式输入

在执行命令时候,默认以空格分割,紧接着在命令后面进行传入的连续字符串。分割方式是空格,若是入参自己带空格,要使用引号或者双引号,进行引用包裹

#!/bin/bash

# ./shell_test var1 var2 var3

#正确获取文件名的真实写法
echo `basename $0`

echo $1
echo $2
echo $3
# 一共有多少个入参
echo $#
# 最后一个入参
echo ${!#}

大部分知识都在上面变量小节介绍过了,上面关键的是最后两行代码:

  • $#返回当前入参是多少个,若是没有命令执行后面的入参,那值为0
  • 虽然目测能够经过${$#}获取最后一个参数,但是,问题是不行,要经过使用${!#}获取

b、"一波带走":处理输入的方式

有时候,咱们会动态获取输入参数,并循环作处理,例以下面的方式:

#!/bin/bash
# shell_test.sh文件
count=1
for var in "$@"
do
	echo "Parameter #$count: $var"
	count=$[ $count + 1 ]
done

# 运行:./shell_test var1 var2
# 结果:
# Parameter #1: var1
# Parameter #2: var2

很简单。再高一个难度:若是咱们想对入参进行分类,区分配置选项与输入参数两种,例如咱们常用的

ls -al不就是有配置选项al。看下面咱们怎么用实际的代码进行实现:

#!/bin/bash
# shell_test.sh文件

while [ -n "$1" ]
do
	case "$1" in
		-a) echo "Found the -a option";;
		-b) echo "Found the -b option";;
		-c) echo "Found the -c option";;
		*) echo "$1 is not an option";;
	esac
	shift
done
<<COMMENT
	输入 /shell_test.sh -a -c -b -d
    结果:
    Found the -a option
    Found the -c option
    Found the -b option
    -d is not an option
COMMENT

shift是shell内部的一个命令,能够左移一个输入参数,被移动的入参,将会消失,例如,入参有var一、var2,shift以后,入参只有var2。shift能够有参数,后面加上一个数字,表示要左移多少个参数。上面的代码用到告终构化的语句,后面章节会进行介绍。接下来咱们再高一个级别:我不只仅想要配置选项的入参,我还想要配置参数的入参,那咱们怎么定义呢?针对输入方式,咱们能够用一个特殊字符来隔离选项与参数,例如'—',看下面:

#!/bin/bash

# shell_test.sh

while [ -n "$1" ]
do
	case "$1" in
		-a) echo "Found the -a option";;
		-b) echo "Found the -b option";;
		-c) echo "Found the -c option";;
		# 这里作了特殊处理:shift掉了--入参,并结束while循环
		--) shift
			break ;;
		*) echo "$1 is not an option";;
	esac
	shift
done

count=1
for var in "$@"
do
	echo "Parameter #$count: $var"
	count=$[ $count + 1 ]
done

<<COMMENT
    输入:./shell_test.sh -a -b -c -- var1 var2 var3
    输出:
    Found the -a option
    Found the -b option
    Found the -c option
    Parameter #1: var1
    Parameter #2: var2
    Parameter #3: var3
COMMENT

已经有点规模了,哈哈。接下来咱们还想进一步,ls -al相似于这种,人家成熟的命令但是能够合并配置选项的,但是上面的代码并不支持。接下来就是最终的解决方案,要用到一个很好的命令:getopts。下面是代码,边写边解说:

#!/bin/bash

while getopts :ab:c opt
do
	case "$opt" in
	a) echo "Found this -a option";;
	b) echo "Found this -b option,with value $OPTARG";;
	c) echo "Found this -c option";;
	*) echo "Unknow";;
	esac
done

shift $[ $OPTIND -1 ]
count=1
for param in "$@"
do
	echo "Parameter $count: $param"
	count=$[ $count+1 ]
done

<<COMMENT
    输入:./shell_test -ab var1 -c var2 var3
    输出:
    Found this -a option
    Found this -b option,with value var1
    Found this -c option
    Parameter 1: var2
    Parameter 2: var3
COMMENT

一波解释:

  • getopts可以保存全部的入参,根据指定的选项来对入参进行格式化:getopts optstring variable
  • optstring配置选项的,选项后面加冒号表明选项有参数要解析。最开始加冒号表示去掉错误信息
  • getopts每次都保存一个配置选项,咱们要循环处理
  • 若是这个选项入参有参数值的话,$OPTARG就是用来存储这个的
  • $OPTIND用来保存当前getopts正在处理的参数位置

至此咱们对于命令传参式输入就全介绍完了,下面开始说用户交互式输入

c、交互式输入

这种比较简单一些,就是用read这个命令一顿瞎几把搞~直接上代码,直接解释:

#!/bin/bash

# 最基本的read,阻塞式的输入,用echo的n(不换行)参数配合打印提示行
echo -n "Enter your name: "
read name 
echo "Hello $name, welcome to my program. "


# 使用read的p参数,直接打印提示航
read -p "Please enter your age: " age 
days=$[ $age * 365 ] 
echo "That makes you over $days days old! "


# 使用read的t参数,来进行超时控制,单位是秒
if read -t 5 -p "Please enter your name: " name 
then
	echo "Hello $name, welcome to my script" 
else
	echo "Sorry, too slow! " 
fi

# 使用read的s参数,能够隐藏输入的字符,好比密码输入
read -s -p "Enter your password: " pass
echo "Is your password really $pass? "

d、文件中输入

文件中的输入,主要仍是运用read这个命令,也是直接代码:

#!/bin/bash 
# reading data from a file # 
count=1
# test是一个文本文件
cat test | while read line 
do
echo "Line $count: $line"
count=$[ $count + 1]
done
echo "Finished processing the file"

line是记录了每一行的记录,咱们能够根据需求,继续对每行的数据进行切割处理

二、输出与重定向

在shell中,输出永远和重定向脱不开干系,由于在shell中不管是输出到屏幕(标准输出)或是输出到文件,都是要靠重定向标准输出来 实现的,因此掌握各类重定向的方式,很关键,一样,这里也是好几种“奇技淫巧”的方式进行重定向。下面我分别说说几个方面。

a、文件描述符

在运行shell脚本的时候,会有一系列的文件描述符,用数字表示,最多有9个,最多见的就是系统设定好的前三个:

  • 0:标准输入(STDIN):默认从键盘读取数据
  • 1:标准输出(STDOUT):默认输出到屏幕
  • 2:标准错误输出(STDERR):默认输出到屏幕

咱们能够重定向这几个,同时也能够本身建立新的文件描述符:

#!/bin/bash

# 默认状况的echo,直接输出到标准输出
echo "echo 2 STDOUT"

# 咱们能够进行重定向标准输出,这样就会输出到具体的文件当中去
echo "echo 2 other" >test.out

## 双大于号的输出表示追加到一个文件,不是覆盖
echo "echo 2 other" >>test.out

# 下面的jiad是没有的目录,标准错误会输出到屏幕
ls /jiad

# 对标准错误输出进行重定向,直接在大于号前面加数字
ls /jiad 2>test.out

# 全局性永久重定向,使用exec命令原理是启动一个新的shell而后进行重定向
exec 2>test.out
ls /jiad #这种错误会直接输出到test.out文件,由于标准错误全局性的重定向了

# 这种就是自定义文件描述符3,而后重定向到test.out文件
exec 3>test.out
# 引用文件描述符的方式,这样输出的结果就覆盖到了test.out文件里面了
echo "echo 2 other" >&3

上面主要的语法都已经列出来,仔细阅读注释就能够了。另外,这地方对重定向的箭头左右是否有空格,要求不是特别的严格,有没有功能都不会受影响,请放心使用。

b、“暂存”文件描述符

当咱们使用exec命令永久性的重定向了标准输出的话,如何再重定向回来本来的呢?也很简单,就如咱们最开始学习编程时候学的“三段式”交换数字同样,先暂存,再操做,最后再反赋值就能够了,就以下面:

#!/bin/bash

exec 3 >&1
exec 1 >test.out

echo "temp save file description"

# 看到没?直接把1再重定向会3,3里面,最开始是重定向到标准输出的
exec 1 >&3

c、输入重定向

输入的文件 标示符是0,重定向使用小于号,简单的一个重定向代码以下:

#!/bin/bash

# 0和符合之间不能有空格,其实他们是一体的,表示:0文件描述符输入重定向
exec 0< out
# 这种状况,cat并不会进行交互式输入了,而会从out文件进行输入
cat

一样,咱们能够经过暂存的方式进行恢复重定向的标准输入

#!/bin/bash
  
# 下面的小于号左右不能有空格存在,不然语法报错,&符号与前面不能有空格
exec 3<&0
exec 0<out

# 这里的cat的输入已经变成了out文件
cat
# 这个命令能够查询0,1,2,3几个文件描述符的指向文件
lsof -a -p $$ -d 0,1,2,3

# 恢复标准输入的文件描述符
exec 0<&3
lsof -a -p $$ -d 0,1,2,3

# 这个时候会变成交互式输入
cat

在输入重定向这里,有个"奇技淫巧",就是咱们可以脚本中动态设置标准输入(重定向),而不用非得使用原生的标准输入或是文件输入,语法以下:

command << delimiter
    document
delimiter

具体咱们举一个例子:

#!/bin/bash 
# read file and create INSERT statements for MySQL

outfile='members.sql'
IFS=',' 
while read lname fname address city state zip 
do
	# 注意这个地方
	cat >> $outfile << EOF
        INSERT INTO members (lname,fname,address,city,state,zip) VALUES ('$lname', '$fname', '$address', '$city', '$state', '$zip'); 
    EOF 
done < ${1}

上面的cat,实际上是直接在命令行执行cat这种模式的,只不过在执行的时候,重定向了两个东西:

  • 输出:使用双大于号方式,进行追加式到文件member.sql文件中
  • 输入:使用了上述的语法,进行了脚本命令式重定向,输入就是两个EOF中间的内容

颇有意思,这种方式,很普遍被运用,例如:shell脚本对mysql数据库执行一条sql语句

4、结构化语句

说到了最后,这一章说完,几乎shell的语法部门就结束了。剩下的,就是深刻大海般的”命令海“!这些命令,有系统自带的,也有咱们本身安装的工具带的命令,或是咱们安装的软件带来的命令,例如git、docker等。针对结构化的问题,也算是放轻松了,由于结构化的语法,是平时咱们最最多见的了。

一、ifelse

这两个东西,在任何计算机语言体系中,可谓是老生常谈了。很少说,下面是基本语法,有两种模式:

# 第一种模式
if command 
then 
	command1
	command2
	......
fi

# 第二种模式
if command; then
	command1
	command2
	......
fi

# 多级别的ifelse
if command1 
then
	commands 
elif command2 
then
	more commands 
else
	more commands
fi

特别注意点:shell中没有所谓的判断真(true)假(false)一说,这里的if语句中的判断语句是一个具体的命令(command)。根据命令的执行退出的状态值来判断是否要执行if里面的代码块: 若是这个命令正确执行,那退出值为0,if代码块就被执行;若是命令执行过程当中发生错误,退出值就不为0,那if代码块就不被执行。这就是shell中的语法。后面的while、for、util等都是这种规则

下面就是基本的shell中if语句的代码:

#!/bin/bash 
# Testing nested ifs - use elif & else 
# 
testuser=NoSuchUser 

if grep $testuser /etc/passwd
then
	echo "The user $testuser exists on this system." 

elif ls -d /home/$testuser 
then
	echo "The user $testuser does not exist on this system."
	echo "However, $testuser has a directory." 

else
	echo "The user $testuser does not exist on this system."
	echo "And, $testuser does not have a directory."
fi

二、ifelse常规使用

虽然ifelse的判断语句是运行一个命令,而后根据退出值来判断的,可是能不能让咱们仍是像以往那样,根据语句的真假来判断是否执行语句块呢?恩,test命令就是解决这个问题的:

if test condition 
then 
	commands 
fi
#!/bin/bash
my_var='jicheng'
if test $my_var
then
	# 这个会被执行,由于test命令会让退出值为0
	echo "myvar"
fi
my_null_var=""
if test 
then 
	# 空的test的退出值不为0,因此这个地方不会被执行
	echo "1"
elif test my_null_var
then
	# test空串也是会退出值不为0的,因此这里也不会执行
	echo "2"
else
	echo "3"
fi

固然,shell中不会傻呵呵让你一直写test的,提供了一个替换语法:

# 注意这里:中括号内部的condition两边必需要有空格,不然语法有错,上面立的flag,在这里能够拔下来了
if [ condition ] 
then 
	commands 
fi

针对性的,test的语法,能够对三种类型进行条件判断下面分三个小节

a、数值比较

因为shell中万物皆字符串,因此对于数值的比较,要使用关键字进行,不难,下面就是:

  • n1 -eq n2:相等
  • n1 -ge n2:大于等于
  • n1 -gt n2:大于
  • n1 -le n2:小于等于
  • n1 -lt n2:小于
  • n1 -ne n2:不相等

下面是简单的例子:

#!/bin/bash

value1=10 
value2=11 
# 
if [ $value1 -gt 5 ] 
then
	echo "The test value $value1 is greater than 5" 
fi 
# 
if [ $value1 -eq $value2 ] 
then
	echo "The values are equal" 
else
	echo "The values are different" 
fi

注意shell中只能处理整数,对于浮点数的test,会报错。不支持

b、字符串比较

其实也不难,主要注意和sort的区别。下面是基础的一些字符串比较表达式:

  • str1 = str2
  • str1 != str2
  • str1 < str2
  • str1 > str2
  • -n str1:检查str1的长度是否非0
  • -z str1:检查str1的长度是否为0

注意点有几个:

  • 注意符号的两边要有空格,不然就是赋值操做了!(前面的flag又拔了)
  • 大于和小于要进行转义,不然就是重定向啦![ "test" \> "Test" ]
  • sort命令对字符排序是根据本地系统的语言设置排序的,小写出如今大写前面
  • 这里的test比较,是根据ASCII顺序的,大写是小于小写的

下面是简单的测试代码:

#!/bin/bash 
# testing string sort order 
val1=Testing 
val2=testing 
#
if [ $val1 \> $val2 ]
then
	echo "$val1 is greater than $val2" 
else
	echo "$val1 is less than $val2" 
fi

c、文件比较

最后一类比较测试颇有多是shell编程中最为强大、也是用得最多的比较形式。它容许你测 试Linux文件系统上文件和目录的状态:

  • -e file:存在
  • -s file:存在且非空
  • -f file:存在且是一个文件
  • -d file:存在且是一个目录
  • -r file:存在且可读
  • -w file:存在且可写
  • -x file:存在且可执行
  • -O file:存在且属于当前用户
  • -G file:存在且属于当前用户组
  • file1 -nt file2:file1比file2新
  • file1 -ot file2:file1比file2旧

d、可使用符合条件表达式

  • [ condition1 ] && [ condition2 ]
  • [ condition1 ] || [ condition2 ]
#!/bin/bash 

if [ -d $HOME ] && [ -w $HOME/testing ] 
then
	echo "The file exists and you can write to it" 
else
	echo "I cannot write to the file" 
fi

e、双小括号与双中括号

双小括号模式是(用于算术运算比较):

(( expression ))

支持:var++、var--、++var、--var、!(非)、~(位求反)、**(幂运算)、<<(左移)、>>(右移)、&(位与)、|(位或)、&&(逻辑与)、||(逻辑或)。另外这种模式下大于小于号不用转义!

双中括号的模式是(用于字符串比较):

[[ expression ]]

双方括号里的expression使用了test命令中采用的标准字符串比较。但它提供了test命 令未提供的另外一个特性——模式匹配(pattern matching)。

#!/bin/bash 

if [[ $USER == r* ]] 
then
	echo "Hello $USER" 
else
	echo "Sorry, I do not know you" 
fi

多说两句:在这里,对于这两个东西,能够玩的花样有点多,平常使用的也会总去使用这两个运算符的花样,这个我在后面单独开文章细讲”奇技淫巧“!

三、case使用

通常高级静态语言中,都有switch的语法,shell中也有。不过这里的使用方式,有点"诡异":

case variable in 
pattern1 | pattern2) commands1;; 
pattern3) commands2;; 
*) default commands;; 
esac

和Java中不一样,这里每一个匹配了但括号里面的模式并执行了command以后,就结束case语句了,并且使用两个;符号结尾。而Java中不使用break的话,会一直匹配下面全部的模式并执行里面代码块。

#!/bin/bash 
# using the case command 
# 
case $USER in 

rich | barbara) 
	echo "Welcome, $USER"
	echo "Please enjoy your visit";;
testing)
	echo "Special testing account";; 
jessica)
	echo "Do not forget to log off when you're done";;
 *)
	echo "Sorry, you are not allowed here";;
esac
<<COMMENT
输出:
Welcome,
rich Please enjoy your visit
COMMENT

四、for语句

for语句在shell中,有两种方式,一种是原生的,一种是C语言风格的。可是C风格的for语句,在一些bash中并非很支持(ubuntu16中的dash就不支持),因此我看,大部分仍是使用原生的for语句为主,咱们这里就先不讲C风格的for。原生的for,在一些小细节上面仍是要注意的,下面是基本的语法:

for var in list 
do 
	commands 
done
注意引号问题
#!/bin/bash
for var in I don't know if this'll work
do
	echo "$var"
done

结果是:

I

dont know if thisll

work

显然不符合咱们的预期,由于shell把两个引号中间的部分当作一个字符串了,有两种解决方法:

#!/bin/bash
# 一种进行转义,一种使用双引号括起来
for var in I don \'t know if "this'll" work
do
	echo "$var"
done
注意字符串总体性
#!/bin/bash
for var in Nevada New Hampshire New Mexico New York
do
	echo "Now going to $var"
done

结果是:

Now going to Nevada

Now going to New

Now going to Hampshire

Now going to New

Now going to Mexico

Now going to New

Now going to York

Now going to North

Now going to Carolina

显然就不符合。咱们可使用双引号括起来:

#!/bin/bash
for var in Nevada "New Hampshire" "New Mexico" "New York"
do
	echo "Now going to $var"
done
注意使用IFS拆列表
#!/bin/bash 
# reading values from a file

file="states"

IFS=$'\n' 
for state in $(cat $file) 
do 
	echo "Visit beautiful $state" 
done

这样,对于文件中的分割,只会使用换行符

注意经常使用的读取目录文件
#!/bin/bash

for file in /home/rich/test/* 
do
	if [ -d "$file" ] 
	then 
		echo "$file is a directory" 
	elif [ -f "$file" ] 
	then 
		echo "$file is a file" 
	fi 
done

注意使用双引号括起来了文件名的引用变量,是由于有可能文件名有空格,若是不括起来,语法就会有错误了!

五、while循环

经历了前面的if与for的历练,对于while这里的语法相对来讲就没有那么难理解了:

while test command 
do 
	other commands 
done

简单的例子:

#!/bin/bash
var1=10
while [ $var1 -gt 0 ] 
do
	echo $var1
	var1=$[ $var1 - 1 ] 
done

六、until循环

这个和while的逻辑相反:while是知道test退出码为非零的时候,结束循环,而这个是当test循环退出码为零的时候结束循环,语法也是相似:

until test command 
do 
	other commands 
done

例子不举了

5、结束语

到此几乎最最基本的shell语法就差很少都讲了。剩下的,就是如"汪洋"的命令大军了。哪怕是这些基础的语法,我都感受,是由命令组成的,这就是shell编程,疯狂的奇技淫巧~接下来我会短平快的写几个小案例和shell中经常使用的命令,例如awk,sed,find等,如如有具体的语法”灵光一现“,都会update到本篇幅中,恩就这样。

相关文章
相关标签/搜索