Liunx之Shell

1 Shell概述

1.1 Shell简介

shell
Liunx内核是一组操作系统软件,用于直接控制和管理整个硬件,为了避免被用户随意的操作,在Linux内核外有个shell层,Shell是一个命令行解释器,用户和其他应用通过shell去访问Linux内核操作硬件。

1.2 系统支持的shell与/etc/shells

由于早年的Unix年代,发展者众,所以由于shell依据发展者的不同就有许多的版本,例如常听到的Bourne SHell (sh) 、在Sun里头预设的C SHell、商业上常用的K SHell、 ,还有TCSH等等,每一种Shell都各有其特点。至于Linux使用的这一种版本就称为 Bourne Again SHell (简称bash) ,这个Shell是Bourne Shell的增强版本,也是基准于GNU的架构下发展出来的,后来由柏克莱大学的Bill Joy设计依附于BSD版的Unix系统中的shell,类似C语言被称为C shell(csh)。目前Linux系统常见的shell有:

  • /bin/sh (Bourne Shell,已经被/bin/bash 所取代)
  • /bin/bash ( Bourne Again Shell,就是Linux 预设的shell)
  • /bin/tcsh (整合C Shell ,提供更多的功能)
  • /bin/csh (C Shell,已经被/bin/tcsh 所取代)

当使用者登入Linux系统时,Linux系统会预设一个shell,针对不同用户预设的shell记录在/etc/passwd档案中。而当前Linux系统支持的shell记录存放在/etc/shells档案中,系统某些服务在运作过程中,会去检查使用者能够使用的shells。

1.3 Bash shell的功能

1.3.1 历史命令记录(history)

  • 当我们以bash 登入Linux 主机之后,系统会主动的从家目录的~/.bash_history 读取以前曾经下过的指令,~/.bash_history记录的最大数量是HISTFILESIZE 这个变量设定值
  • 登入系统时执行的指令只会记录在内存中,只有登出系统时,系统才会将最近的HISTFILESIZE笔记录到~/.bash_history
  • 也可以用history -w强制立刻写入的
#history [n]
#history [-c]
#history [-raw] histfiles 

# n :数字,列出最近的n笔命令列表
#-c :将目前的shell中的所有history内容全部消除
#-a :将目前新增的history指令新增入histfiles中,预设写入~/.bash_history中
#-r :将histfiles的内容读到目前这个shell 的history内存中
#-w :将目前的history内存中的内容写入histfiles中

[yut@iZnq8v4wpstsagZ ~]$ history 3		#列出最新3条历史记录
 1019 ls -al
 1020 pwd
 1021 history 3
[yut@iZnq8v4wpstsagZ ~]$ !1019			#重新执行ls -al指令
[yut@iZnq8v4wpstsagZ ~]$ !!				#执行上条指令
[yut@iZnq8v4wpstsagZ ~]$ !ls			#执行最近以ls开头的指令

1.3.2 命令与档案补全功能([tab])

  • [Tab] 接在一串指令的第一个字的后面,则为命令补全
  • [Tab] 接在一串指令的第二个字以后时,则为档案补齐
  • 若安装bash-completion软件,则在某些指令后面使用[tab] 按键时,可以进行『选项/参数的补齐』功能

1.3.3 命令别名设定功能(alias)

通过alias命令可查看和设置系统的命令别名,通过unalias取消命令别名

#查看命令别名
[yut@iZnq8v4wpstsagZ ~]$ alias
alias egrep='egrep --color=auto'
alias fgrep='fgrep --color=auto'
alias grep='grep --color=auto'
alias l.='ls -d .* --color=auto'
alias ll='ls -l --color=auto'
alias ls='ls --color=auto'
alias vi='vim'
alias which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'
#设置命令别名
[yut@iZnq8v4wpstsagZ ~]$ alias lla='ls -al'
[yut@iZnq8v4wpstsagZ ~]$ lla
#取消命令别名
[yut@iZnq8v4wpstsagZ ~]$ unalias lla

1.3.4 工作控制、前景背景控制(job control, foreground, background)

详见:http://linux.vbird.org/linux_basic/0440processcontrol.php#background

1.3.5 程式化脚本(shell scripts)

在DOS年代还记得将一堆指令写在一起的所谓的『批次档』吧?在Linux底下的shell scripts则发挥更为强大的功能,可以将你平时管理系统常需要下达的连续指令写成一个档案,该档案并且可以透过对谈互动式的方式来进行主机的侦测工作。

1.3.6 万用字元(Wildcard)

可以使用万用字元查询某些相似档案

[yut@iZnq8v4wpstsagZ ~]$ ls /e*
符号意义
*代表0 个到无穷多个任意字元
?代表一定有一个任意字元
[ ]代表括号内的字元其中一个字元。例如[abcd] 代表一定有一个字元, 可能是a, b, c, d 这四个任何一个
[ - ]若有减号在中括号内时,代表在编码顺序内的所有字元。例如[0-9] 代表 0 到9 之间的所有数字,因为数字的语系编码是连续的
[^ ]若中括号内的第一个字元为指数符号(^) ,那表示反向选择,例如[^abc] 代表一定有一个字元,只要是非a, b, c 的其他字元即可

1.4 Bash shell 的内建指令查询(type)

直接使用man bash指令时会发现有其它指令的说明文档,这是由于为了方便shell的操作,bash中已经内建了很多指令,比如cdpwd等等。可以通过type指令查看指令是否属于bash内建指令。

#type语法:type [-tpa] name
#不加任何选项与参数时,type 会显示出name 是外部指令还是bash内建指令
#-t:type会将name以底下这些字眼显示出他的意义:
#      file :表示为外部指令
#      alias :表示该指令为命令别名所设定的名称
#      builtin :表示该指令为bash内建的指令功能
#-p:如果后面接的name 为外部指令时,才会显示完整档名
#-a:从PATH变量定义的路径中,将所有含name的指令都列出来,包含alias
[yut@iZnq8v4wpstsagZ ~]$ type ls
ls is aliased to `ls --color=auto'

1.5 指令的下达与快速编辑按钮

组合键功能与示范
+[Enter]换行输入指令
[ctrl]+u/[ctrl]+k分别是从游标处向前删除指令串([ctrl]+u) 及向后删除指令串([ctrl]+k)
[ctrl]+a/[ctrl]+e分别是让游标移动到整个指令串的最前面([ctrl]+a) 或最后面([ctrl]+e)

1.6 输入输出重定向

命令说明
command > file将输出重定向到 file。
command < file将输入重定向到 file。
command >> file将输出以追加的方式重定向到 file。
n > file将文件描述符为 n 的文件重定向到 file。
n >> file将文件描述符为 n 的文件以追加的方式重定向到 file。
n >& m将输出文件 m 和 n 合并。
n <& m将输入文件 m 和 n 合并。
<< tag将开始标记 tag 和结束标记 tag 之间的内容作为输入。

文件描述符 0 通常是标准输入(STDIN),1 是标准输出(STDOUT),2 是标准错误输出(STDERR)。

示例:

#资料流输出到文件
[yut@iZnq8v4wpstsagZ ~]$ ll / > ~/rootfile
#将标准输出和标准错误输出到不同文件
[yut@iZnq8v4wpstsagZ ~]$ find /home -name .bashrc 1> list_right 2> list_error
#将标准错误输出重导向到标准输出,输出到一个文件
[yut@iZnq8v4wpstsagZ ~]$ find /home - name .bashrc > list 2>&1
[yut@iZnq8v4wpstsagZ ~]$ find /home -name .bashrc &> list

#cat >创建新档案,将.bashrc内容作为输入到catfile
[yut@iZnq8v4wpstsagZ ~]$ cat > catfile < ~/.bashrc 
#
[yut@iZnq8v4wpstsagZ ~]$ cat > catfile << "eof"
this is << test
eof

1.7 管线命令

管线命令使用的是 | 这个界定符号,后一个命令的标准输入是前一个命令的标准输出。
在这里插入图片描述

1.7.1 截取和过滤命令

1.7.1.1 cut

cut 是切的意思,这个指令可以将一段信息的某一段给它切出来,处理信息的方式是以行为单位。

#cut -d '分隔字元' -f fields
#cut -c 字元区间

#-d :后面接分隔字元,与-f 一起使用
#-f :依据-d 的分隔字元将一段信息分割成为数段,用-f取出第几段的意思,多个以,分割
#-c :以字元(characters) 的单位取出固定字元区间

#以:分割,取第5个分割字段
[yut@iZnq8v4wpstsagZ ~]$ echo ${PATH} | cut -d ':' -f 5
#以:分割,取第3个和第5个分割字段
[yut@iZnq8v4wpstsagZ ~]$ echo ${PATH} | cut -d ':' -f 3,5

#每行截取从第12个字符到最后一个字符
[yut@iZnq8v4wpstsagZ ~]$ export | cut -c 12-
#每行截取从第12个字符到第20个字符
[yut@iZnq8v4wpstsagZ ~]$ export | cut -c 12-20
1.7.1.2 grep
#grep [-acinv] [--color=auto] '搜寻字串' filename 

#-a :将binary 档案以text 档案的方式搜寻资料
#-c :计算找到'搜寻字串' 的次数
#-i :忽略大小写的不同,所以大小写视为相同
#-n :顺便输出行号
#-v :反向选择,亦即显示出没有'搜寻字串' 内容的那一行
#--color=auto :可以将找到的关键字部分加上颜色的显示

#取出/etc/man_db.conf内含MANPATH的那几行
[yut@iZnq8v4wpstsagZ ~]$ grep --color=auto 'MANPATH' /etc/man_db.conf
#输入所有root用户登入的行信息
[yut@iZnq8v4wpstsagZ ~]$ last | grep 'root'
#输入非root用户登入的行信息
[yut@iZnq8v4wpstsagZ ~]$ last | grep -v 'root'

1.7.2 排序、去重和统计

1.7.2.1 sort
#sort [-fbMnrtuk] [file or stdin] 

#-f :忽略大小写的差异,例如A 与a 视为编码相同
#-b :忽略最前面的空白字元部分
#-M :以月份的名字来排序,例如JAN, DEC 等等的排序方法
#-n :使用『纯数字』进行排序(预设是以文字型态来排序的)
#-r :反向排序
#-u :就是uniq ,相同的资料中,仅出现一行代表
#-t :分隔符号,预设是用[tab] 键来分隔
#-k :以那个区间(field) 来进行排序的意思

#sort预设是以每行的第一个字符排序,
[yut@iZnq8v4wpstsagZ ~]$ cat /etc/passwd | sort
#利用last ,将输出的资料仅取帐号,并加以排序
[yut@iZnq8v4wpstsagZ ~]$ last | cut -d ' ' -f1 | sort
1.7.2.2 uniq

排序完成了,想要将重复的资料仅列出一个显示,可以使用uniq

#uniq [-ic] 

#-i :忽略大小写字元的不同
#-c :进行计数

#利用last ,将输出的资料仅取帐号排序和去重
[yut@iZnq8v4wpstsagZ ~]$ last | cut -d ' ' -f1 | sort | uniq
#利用last ,将输出的资料仅取帐号排序和分组计数
[yut@iZnq8v4wpstsagZ ~]$ last | cut -d ' ' -f1 | sort | uniq -c
1.7.2.3 wc
#wc [-lwm] 

#-l :统计行数
#-w :统计单词或字(word)数
#-m :统计字节或字符数

#统计man_db.conf的行数、字数和字符数
[yut@iZnq8v4wpstsagZ ~]$ cat /etc/man_db.conf | wc -lwm

1.7.3 双向重导向

tee可以将资料流输出到控制台的同时重导向到档案。
tee

#tee [-a] file 
#-a :以累加(append) 的方式,将资料加入file中

#以累加的方式输出到homefile档案
[yut@iZnq8v4wpstsagZ ~]$ ls -l / | tee -a ~/homefile | more

1.7.4 字元转换命令

1.7.4.1 tr

tr可以用来删除一段讯息当中的文字,或者是进行文字讯息的替换

#tr [-ds] SET1 ... 

#-d :删除讯息当中的SET1这个字串
#-s :取代掉重复的字元

#所有小写转换成大写
[yut@iZnq8v4wpstsagZ ~]$ last | tr '[az]' '[AZ]'
#删除:
[yut@iZnq8v4wpstsagZ ~]$ cat /etc/passwd | tr -d ':'
#切换
[yut@iZnq8v4wpstsagZ ~]$ ll /etc/passwd ~/passwd* 
1.7.4.2 col

col用于替换[tab]键和过滤一些控制字符。

#col [-xbf] 

#-x :将[tab]键转换成对等的空格键
#-b 过滤掉所有的控制字符,包括RLF和HRLF
#-f 滤除RLF字符,但允许将HRLF字符呈现出来

#将[tab]键转换成空格键,并使用cat -A显示出所有特殊按键
[yut@iZnq8v4wpstsagZ ~]$ cat /etc/man_db.conf | col -x | cat -A | more
1.7.4.3 join

join用于处理两个档案之间的资料,如果二个档案第一个栏位的资料相同,则将它们合并成一行。

#join [-ti12] file1 file2 

#-t :分割符,join预设的分割符是空格,并且比对『第一个栏位』的资料,如果两个档案相同,则将两笔资料联成一行,且第一个栏位放在第一个
#-i :忽略大小写的差异
#-1 :第一个档案要用那个栏位来分析
#-2 :第二个档案要用那个栏位来分析

#用:分割,比对用户名合并成一行
[yut@iZnq8v4wpstsagZ ~]$ join -t ':' /etc/passwd /etc/shadow | head -n 3
#用:分割,第一个资料用第4栏位,第二个资料用第3栏位比对,合并成一行
[yut@iZnq8v4wpstsagZ ~]$ join -t ':' -1 4 /etc/passwd -2 3 /etc/group | head -n 3
1.7.4.4 paste

这个paste就要比join简单多了!相对于join必须要比对两个档案的资料相关性, paste就直接将同一行合并成一行,且中间以[tab]键隔开。

#paste [-d] file1 file2 

#-d :后面可以接分隔字元。预设是以[tab] 来分隔的
#- :如果file 部分写成- ,表示来自standard input的资料的意思

#将二个档案同一行合并成一行
[yut@iZnq8v4wpstsagZ ~]$ paste /etc/passwd /etc/shadow 
#将cat输出作为输入,三个档案同一行合并成一行
[yut@iZnq8v4wpstsagZ ~]$ cat /etc/group|paste /etc/passwd /etc/shadow -|head -n 3 
1.7.4.5 expand

将[tab]按键转成一个空白键

#expand [-t] file 

#-t :后面可以接数字。一般来说,一个[tab]按键可以用8 个空白键取代,可以自定义

#将[tab]键转换成6个空格
[yut@iZnq8v4wpstsagZ ~]$ grep '^MANPATH' /etc/man_db.conf | head -n 3 | expand -t 6 - | cat -A

1.7.5 分割命令

split将大档案根据一定规则分割成多个小档案

#split [-bl] file PREFIX 

#-b :后面可接欲分割成的档案大小,可加单位,例如b, k, m 等;
#-l :以行数来进行分割。
#PREFIX :代表前置字元的意思,可作为分割档案的前导文字。

#每300k分割成一个以services为前缀的档案
[yut@iZnq8v4wpstsagZ ~]$ split -b 300k /etc/services services
#每10行分割成一个档案
[yut@iZnq8v4wpstsagZ ~]$ ls -al / | split -l 10 - lsroot

1.7.6 参数代换: xargs

#xargs [-0epn] command 

#-0 :如果输入的stdin 含有特殊字元,例如`, \, 空白键等等字元时,这个-0 参数可以将他还原成一般字元。这个参数可以用于特殊状态
#-e :这个是EOF (end of file) 的意思。后面可以接一个字串,当xargs 分析到这个字串时,就会停止继续工作
#-p :在执行每个指令的argument 时,都会询问使用者的意思
#-n :后面接次数,每次command 指令执行时,要使用几个参数的意思
#当xargs 后面没有接任何的指令时,预设是以echo 来进行输出

1.7.7 关于减号- 的用途

在管线命令当中,常常会使用到前一个指令的stdout 作为这次的stdin , 某些指令需要用到档案名称(例如tar) 来进行处理时,该stdin 与stdout 可以利用减号"-" 来替代。
例:

[yut@iZnq8v4wpstsagZ ~]$ mkdir /tmp/homeback
[yut@iZnq8v4wpstsagZ ~]$ tar -cvf - /home | tar -xvf - -C /tmp/homeback

/home里面的档案给他打包,但打包的资料不是纪录到档案,而是传送到stdout; 经过管线后,将tar -cvf - /home传送给后面的tar - xvf -。后面的这个-则是取用前一个指令的stdout, 因此,我们就不需要使用filename。

2 Shell变量

2.1 变量的相关操作

2.1.1 变量取用

可以通过echo指令输出变量值,如果变量没有设定则输出“空”,变量的取用需要在变量前加上“$”。语法如下:

  • echo $variable
  • echo ${variable}(推荐)
[yut@iZnq8v4wpstsagZ ~]$ echo ${HOME}
/home/yut

2.1.2 变数的设定

变量的设定通过“=”赋值,例:

[yut@iZnq8v4wpstsagZ ~]$ echo ${myname}

[yut@iZnq8v4wpstsagZ ~]$ myname=yut
[yut@iZnq8v4wpstsagZ ~]$ echo ${myname}
yut

设定规则如下:

  1. “=”两边不能直接接空格
  2. 变量名称只能是英文字母与数字,但是开头字元不能是数字
  3. 变量值如果有空格可以使用双引号或单引号,双引号内的特殊字元如“$"等可以保留原本特性,但单引号内的特殊字元仅为一般字元(纯文字)
  4. 可使用跳脱字元“\”将特殊字元变成一般字元,如:myname=yu\ tao
  5. 在一串指令中如果需要其它指令提供信息时,可以使用反单引号`command`或$(command),如:version=$(uname -r)
  6. 如果变量需要增加内容,可以使用${variable}或$variable累加内容,如:PATH="$PATH":/home/binPATH=${PATH}:/home/bin
  7. 如果变量需要在其它子程序执行,需要使用export将变量变成环境变量,如:export myname
  8. 常大写字元为系统预设变量,自定义变量可以使用小写字元,方便判断

子程序解释:
当使用者登入Linux系统会获取一个预设的shell,这个shell就是一个独立的程序,如果在这个shell下执行bash指令则启用了另一个新的shell,新的shell就是子程序,而原来的shell则为父程序。一般情况下,父程序的自定义变量在子程序内是无法访问的,但是通过export则可以让父程序内自定义的变量在子程序中使用。

shell

2.1.3 删除变量

使用 unset 命令可以删除变量。语法:

unset variable_name

示例:

[yut@iZnq8v4wpstsagZ ~]$ unset myname

2.1.4 只读变量

使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变,也不能unset,只能通过登入登出重置了。
示例:

[yut@iZnq8v4wpstsagZ ~]$ myname="yut"
[yut@iZnq8v4wpstsagZ ~]$ readonly myname

2.2 变量作用域

运行shell时,会同时存在三种变量:

  1. 局部变量:局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。
  2. 环境变量:所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。
  3. shell变量:shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行

2.2.1 查询环境变量(env)

env 是environment (环境) 的简写,列出所有的环境变量。

#列出目前的shell环境下的所有环境变数与其内容
[yut@iZnq8v4wpstsagZ ~]$ env
HOSTNAME=iZnq8v4wpstsagZ					#主机名称
SHELL=/bin/bash								#当前shell
TERM=xterm									#终端机使用的环境类型
HISTSIZE=1000								#记录历史指令的条数,centos预设1000
USER=yut									#当前用户
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lh...									#一些颜色显示
PWD=/home/yut								#当前工作目录
LANG=en_US.UTF-8							#语系
HISTCONTROL=ignoredups						#历史控制
HOME=/home/yut								#当前用户家目录
LOGNAME=yut									#当前登录用户账号名
_=/bin/env									#上一次使用的指令的最后一个参数(或指令本身)

2.2.2 查询环境变量和局部变量(set)

bash含有环境变量、bash操作相关的shell变量和自定义局部变量,可以通过set指令将这些都显示出来。

[yut@iZnq8v4wpstsagZ ~]$ set
BASH=/bin/bash                       					#bash的主程式放置路径
BASH_VERSINFO=([0]="4" [1]="2" [2]="46" [3]="1" [4]="release" [5]="x86_64-redhat-linux-gnu ") BASH_VERSION='4.2.46(1)-release' 												#bash的版本
COLUMNS=90                           					#在目前的终端机环境下,使用的栏位有几个字元长度
HISTFILE=/home/yut/.bash_history   						#历史命令记录的放置档案,隐藏档 
HISTFILESIZE=1000                     					#存起来(与上个变量有关)的档案之指令的最大纪录数。
HISTSIZE=1000                         					#目前环境下,记忆体中记录的历史命令最大数。
IFS=$' \t\n'                           					#预设的分隔符号 
LINES=20                              				 	#目前的终端机下的最大行数 
MACHTYPE=x86_64-redhat-linux-gnu     					#安装的机器类型 
OSTYPE= linux-gnu                       				#作业系统的类型
PS1='[\u@\h \W]\$ '                    					#命令提示字元,也就是我们常见的[root@www ~]#
PS2='> '                               					#如果你使用跳脱符号(\)第二行以后的提示字元
$                                      					#当前shell的PID 
?                                     					#刚刚执行完指令的回传值

BASH操作相关的指令通常也会被设定为大写字元,以下是一些指令的详细说明:

PS1(提示字元的设定):

  • \d :可显示出『星期月日』的日期格式,如:“Mon Feb 2”
  • \H :完整的主机名称
  • \h :仅取主机名称在第一个小数点之前的名字
  • \t :显示时间,为24 小时格式的『HH:MM:SS』
  • \T :显示时间,为12 小时格式的『HH:MM:SS』
  • \A :显示时间,为24 小时格式的『HH:MM』
  • @ :显示时间,为12 小时格式的『am/pm』样式
  • \u :目前使用者的帐号名称
  • \v :BASH 的版本资讯
  • \w :完整的工作目录名称,由根目录写起的目录名称。但家目录会以~ 取代;
  • \W :利用basename 函数取得工作目录名称,所以仅会列出最后一个目录名。
  • # :下达的第几个指令。
  • $ :提示字元,如果是root 时,提示字元为# ,否则就是$
[yut@iZnq8v4wpstsagZ ~]$ SPS1='[\u@\h \w \A #\#]\$'
[yut@iZnq8v4wpstsagZ ~ 16:59 #6]#

$:表示当前shell的PID

[yut@iZnq8v4wpstsagZ ~]$ echo $$
19507

?:关于上个执行指令的回传值

[yut@iZnq8v4wpstsagZ ~]$ echo $SHELL		#未发生错误
/bin/bash
[yut@iZnq8v4wpstsagZ ~]$ echo $?			#上个指令未发生错误,返回0
0
[yut@iZnq8v4wpstsagZ ~]$ 12myname=yut		#发生错误
-bash: 12myname=yut: command not found
[yut@iZnq8v4wpstsagZ ~]$ echo $?			#上个指令发生错误,返回一个非0的数字
127
[yut@iZnq8v4wpstsagZ ~]$ echo $?			#上个指令未发生错误,返回0
0

2.2.3 自定义局部变量转为环境变量(export)

环境变量与自定义变量直接的区别在于是否可以被子程序所使用,export除了拥有和env相同的功能,还可以将自定义变量转换为环境变量。

查看环境变量:

[yut@iZnq8v4wpstsagZ ~]$ export
declare -x HISTCONTROL="ignoredups"
declare -x HISTSIZE="1000"
declare -x HOME="/home/yut"
declare -x HOSTNAME="iZnq8v4wpstsagZ"
declare -x LANG="en_US.UTF-8"
declare -x LOGNAME="yut"
...

自定义局部变量转换成环境变量:

[yut@iZnq8v4wpstsagZ ~]$ echo $myname

[yut@iZnq8v4wpstsagZ ~]$ myname=yut			#定义变量
[yut@iZnq8v4wpstsagZ ~]$ echo $myname
yut
[yut@iZnq8v4wpstsagZ ~]$ bash				#启用一个新的bash(子程序)
[yut@iZnq8v4wpstsagZ ~]$ echo $myname		#无法访问父程序自定义变量

[yut@iZnq8v4wpstsagZ ~]$ exit				#离开子程序
exit
[yut@iZnq8v4wpstsagZ ~]$ export myname		#将自定义变量转换为环境变量
[yut@iZnq8v4wpstsagZ ~]$ bash				#启用一个新的bash(子程序)
[yut@iZnq8v4wpstsagZ ~]$ echo $myname		#可以访问父程序变量
yut
[yut@iZnq8v4wpstsagZ ~]$ unset myname		#子程序取消变量
[yut@iZnq8v4wpstsagZ ~]$ echo $myname

[yut@iZnq8v4wpstsagZ ~]$ exit				#离开子程序
exit
[yut@iZnq8v4wpstsagZ ~]$ echo $myname		#父程序没影响
yut

上述情况可解释:

  • 当启动一个shell,作业系统会分配一块内存给shell 使用,这块内存内的变量可让子程序取用
  • 若在父程序利用export 功能,可以让自定义变量写到上述的内存中(环境变量)
  • 当载入另一个shell 时(亦即启动子程序,而离开原本的父程序了),子shell 可以将父shell 内存中的环境变量复制到自己的环境变量内存中。

2.3 变量数据类型

2.3.1 shell字符串

shell字符串支持双引号和单引号,它们的区别如下:

  • 单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的
  • 单引号字串中不能出现单独一个的单引号(对单引号使用转义符后也不行),但可成对出现,作为字符串拼接使用。
[yut@iZnq8v4wpstsagZ ~]$ string="abcdefg"
#获取字符串长度
[yut@iZnq8v4wpstsagZ ~]$ echo ${#string}
#截取字符串,从索引1处获取4个字符
[yut@iZnq8v4wpstsagZ ~]$ echo ${string:1:4}
#查找字符 i 或 o 的位置
[yut@iZnq8v4wpstsagZ ~]$ echo `expr index "$string" io`

2.3.2 shell数组

bash支持一维数组(不支持多维数组),并且没有限定数组的大小,数组元素的下标由 0 开始。

#定义一个数组
[yut@iZnq8v4wpstsagZ ~]$ arrA=(10 9 12 13)

#逐一定义数组的元素
[yut@iZnq8v4wpstsagZ ~]$ arrB[0]=44
[yut@iZnq8v4wpstsagZ ~]$ arrB[1]=43
[yut@iZnq8v4wpstsagZ ~]$ arrB[2]=42

#读取数组单个元素
[yut@iZnq8v4wpstsagZ ~]$ echo ${arrA[1]}

#获取数组中所有元素
[yut@iZnq8v4wpstsagZ ~]$ echo ${arrA[@]}

#获取数组元素个数
[yut@iZnq8v4wpstsagZ ~]$ echo ${#arrA[@]}
[yut@iZnq8v4wpstsagZ ~]$ echo ${#arrA[*]}

#获取数组单个元素的长度
[yut@iZnq8v4wpstsagZ ~]$ echo ${#arrA[1]}

2.3.3 shell数值

bash shell预设的变量数据类型为字符串,并且数值运算仅能到达整数形态,如:var=1+2,结果为1+2,数值运算1/3 结果是0。

2.4 变量声明

2.4.1 键盘读取(read)

read指令可以读取来自键盘输入的变量。这个指令最常被用在shell script 的撰写当中, 想要跟使用者对谈。

#read [-pt] variable 
#-p :后面可以接提示文字
#-t :后面可以接等待的输入的秒数
[yut@iZnq8v4wpstsagZ ~]$ read -p "Please keyin your name: " -t 10 name
Please keyin your name: yut
[yut@iZnq8v4wpstsagZ ~]$ echo ${name}
yut

2.4.2 declare / typeset

declare或typeset是一样的功能,都是声明变量的类型。如果使用declare后面并没有接任何参数,那么bash就会主动的列出所有变量名称与值,就好像使用set一样。

#declare [-aixr] variable 
#-a :将后面名为variable 的变量定义成为数组(array) 类型
#-i :将后面名为variable 的变量定义成为整数(integer) 类型
#-x :用法与export一样,将后面的variable 变成环境变数
#+x :将后面的variable 环境变数转换成局部变量
#-r :将变量设定成为readonly类型,该变量不可被更改值,也不能unset,只能通过登入登出重置了
#-p : 列出变量的类型
[yut@iZnq8v4wpstsagZ ~]$ sum=100+300+50
[yut@iZnq8v4wpstsagZ ~]$ echo ${sum}
100+300+50
[yut@iZnq8v4wpstsagZ ~]$ declare -i sum=100+300+50
[yut@iZnq8v4wpstsagZ ~]$ echo ${sum}
450

[yut@iZnq8v4wpstsagZ ~]$ declare -x sum		#转换成环境变量
[yut@iZnq8v4wpstsagZ ~]$ declare +x sum		#取消环境变量
[yut@iZnq8v4wpstsagZ ~]$ declare -r sum		#设置成唯读
[yut@iZnq8v4wpstsagZ ~]$ declare -p sum		#列出变量的类型

2.5 语系变量(locale)

目前大多数的Linux distributions 已经都是支持日渐流行的万国码了,也都支援大部分的国家语系,我们可以通过locale指令查看当前Linux支持的语系。

[yut@iZnq8v4wpstsagZ ~]$ locale -a
zh_TW
zh_TW.big5      #大五码的中文编码
zh_TW.euctw
zh_TW.utf8      #万国码的中文编码
zu_ZA
zu_ZA.iso88591
zu_ZA.utf8
...

locale不带参数查看当前Linux的语系设置:

[yut@iZnq8v4wpstsagZ ~]$ locale
LANG=en_US.UTF-8
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_PAPER="en_US.UTF-8"
LC_NAME="en_US.UTF-8"
LC_ADDRESS="en_US.UTF-8"
LC_TELEPHONE="en_US.UTF-8"
LC_MEASUREMENT="en_US.UTF-8"
LC_IDENTIFICATION="en_US.UTF-8"
LC_ALL=

语系的设置档是/etc/locale.conf,一般只要设置LANG或LC_ALL即可取代其它的变量设置

2.6 变量值的删除、取代与替换

变量除了可以直接设定来修改原本的值之外,还可以透过一些简单的动作来将变量的值进行删除、取代与替换

2.6.1 删除和取代

[yut@iZnq8v4wpstsagZ ~]$ path=${PATH}
[yut@iZnq8v4wpstsagZ ~]$ echo ${path} 
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/yut/.local/bin:/home/yut/bin
[yut@iZnq8v4wpstsagZ ~]$ echo ${path#/*local/bin:}			#从左边开始向右删除到第一个匹配的字符串
/usr/bin:/usr/local/sbin:/usr/sbin:/home/yut/.local/bin:/home/yut/bin
[yut@iZnq8v4wpstsagZ ~]$ echo ${path##/*:}					#从左边开始向右删除到最后一个匹配的字符串
/home/yut/bin
[yut@iZnq8v4wpstsagZ ~]$ echo ${path%:*bin}					#从右边开始向左删除到第一个匹配的字符串
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/yut/.local/bin
[yut@iZnq8v4wpstsagZ ~]$ echo ${path%%:*bin}				#从右边开始向左删除到最后一个匹配的字符串
/usr/local/bin

[yut@iZnq8v4wpstsagZ ~]$ echo ${path/sbin/SBIN}			#替换第一个匹配的sbin为SBIN
/usr/local/bin:/usr/bin:/usr/local/SBIN:/usr/sbin:/home/yut/.local/bin:/home/yut/bin
[yut@iZnq8v4wpstsagZ ~]$ echo ${path//sbin/SBIN}		#替换所有匹配的sbin为SBIN
/usr/local/bin:/usr/bin:/usr/local/SBIN:/usr/SBIN:/home/yut/.local/bin:/home/yut/bin
变量设定方式说明
${variable#keyword}从左边开始向右删除到第一个匹配的keyword
${variable##keyword}从左边开始向右删除到最后一个匹配的keyword
${variable%keyword}从右边开始向左删除到第一个匹配的keyword
${variable%keyword}从右边开始向左删除到最后一个匹配的keyword
${variable/old_str/new_str}替换第一个匹配的old_str为new_str
${variable//old_str/new_str}替换所有匹配的old_str为new_str

2.6.2 变量的测试与值替换

在某些时刻我们常常需要判断某个变量是否存在,若变量存在则使用既有的值,若变量不存在则给予一个默认值

[yut@iZnq8v4wpstsagZ ~]$ echo ${username}

[yut@iZnq8v4wpstsagZ ~]$ username=${username-root}
[yut@iZnq8v4wpstsagZ ~]$ echo ${username} 
root
[yut@iZnq8v4wpstsagZ ~]$ username="yutao"
[yut@iZnq8v4wpstsagZ ~]$ username=${username-root}
[yut@iZnq8v4wpstsagZ ~]$ echo ${username} 
yutao
[yut@iZnq8v4wpstsagZ ~]$ username=""
[yut@iZnq8v4wpstsagZ ~]$ username=${username:-root}			#检查空和空字符串,如果符合则用root替代

3 运算符

Shell 和其他编程语言一样,支持多种运算符,包括:

  • 算术运算符
  • 关系运算符
  • 布尔运算符
  • 逻辑运算符
  • 字符串运算符
  • 文件测试运算符
[yut@iZnq8v4wpstsagZ ~]$ test ! -e multiplying.sh && echo "not exist"|| echo "exist"
exist

3.1 算术运算符

3.1.1 运算符

假定变量 a 为 10,变量 b 为 2

运算符说明举例
+加法`expr $a + $b` 结果为 30。
-减法`expr $a - $b` 结果为 -10。
*乘法`expr $a \* $b` 结果为 200。
/除法`expr $b / $a` 结果为 2。
%取余`expr $b % $a` 结果为 0。
=赋值a=$b 将把变量 b 的值赋给 a。
==相等用于比较两个数字,相同则返回 true。 [ $a == $b ] 返回 false。
!=不相等用于比较两个数字,不相同则返回 true。 [ $a != $b ] 返回 true。

注意:乘号(*)前边必须加反斜杠()才能实现乘法运算

3.1.2 数值运算

前面已经提过declare -i可以声明变量的类型为整数以完成运算,我们也可以使用$((表达式))(推荐)或$[表达式]来完成数值运算,但是bash预设精度仅支持整数。

[yut@iZnq8v4wpstsagZ ~]$ vim multiplying.sh
#!/bin/bash
read -p "first number: " firstnu
read -p "second number: " secnu

#使用declare运算
echo -e "declare example"
declare -i total=${firstnu}*${secnu}
echo -e "the result of declare ${firstnu} x ${secnu} is ==> ${total}"

#使用$((运算式))
echo -e "$ (()) example"
total1=$((${firstnu}*${secnu}))
echo -e "the result of $ (())  ${firstnu} x ${secnu} is ==> ${total1}"

bash还提供了expr指令来计算整数

#expr支持+,-,*,/,%运算符,运算符二边需要有空格
[yut@iZnq8v4wpstsagZ ~]$ expr 2 + 3
5

以上三种运算方式参数均不支持小数,如果需要计算含有小数的计算式,可以借助bc指令:

[yut@iZnq8v4wpstsagZ ~]$ echo "3.4*2"|bc
6.8

3.2 判断式

Linux支持二种方式判断:

  • [ 运算表达式 ]
  • test指令

测试档案是否存在二种方式的示例:


[yut@iZnq8v4wpstsagZ ~]$ test ! -e multiplying.sh && echo "not exist"|| echo "exist"
exist
[yut@iZnq8v4wpstsagZ ~]$ [ ! -e multiplying.sh ] && echo "not exist"|| echo "exist"
exist

注意:

  • 在中括号[] 内的每个单元都需要有空白键来分隔;
  • 在中括号内的变量,最好都以双引号括号起来;
  • 在中括号内的常量,最好都以单或双引号括号起来

3.2.1 关系运算符

关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
假定变量 a 为 10,变量 b 为 2

运算符说明举例
-eq检测两个数是否相等,相等返回 true。[ $a -eq $b ] 返回 false。
-ne检测两个数是否不相等,不相等返回 true。[ $a -ne $b ] 返回 true。
-gt检测左边的数是否大于右边的,如果是,则返回 true。[ $a -gt $b ] 返回 false。
-lt检测左边的数是否小于右边的,如果是,则返回 true。[ $a -lt $b ] 返回 true。
-ge检测左边的数是否大于等于右边的,如果是,则返回 true。[ $a -ge $b ] 返回 false。
-le检测左边的数是否小于等于右边的,如果是,则返回 true。[ $a -le $b ] 返回 true。

3.2.2 布尔运算符

假定变量 a 为 10,变量 b 为 2

运算符说明举例
!非运算,表达式为 true 则返回 false,否则返回 true。[ ! false ] 返回 true。
-o或运算,有一个表达式为 true 则返回 true。[ $a -lt 20 -o $b -gt 100 ] 返回 true。
-a与运算,两个表达式都为 true 才返回 true。[ $a -lt 20 -a $b -gt 100 ] 返回 false。

3.2.3 逻辑运算符

假定变量 a 为 10,变量 b 为 2

运算符说明举例
&&逻辑的 AND[[ $a -lt 100 && $b -gt 100 ]] 返回 false
||逻辑的 OR[[ $a -lt 100 || $b -gt 100 ]] 返回 true

3.2.4 字符串运算符

假定变量 a 为 “abc”,变量 b 为 “efg”

运算符说明举例
=/==检测两个字符串是否相等,相等返回 true。[ $a = $b ] 返回 false。
!=检测两个字符串是否相等,不相等返回 true。[ $a != $b ] 返回 true。
-z检测字符串长度是否为0,为0返回 true。[ -z $a ] 返回 false。
-n检测字符串长度是否为0,不为0返回 true。[ -n “$a” ] 返回 true。
$检测字符串是否为空,不为空返回 true。[ $a ] 返回 true。

3.2.5 文件测试运算符

文件测试运算符用于检测 Unix 文件的各种属性

运算符说明举例
-e检测文件(包括目录)是否存在,如果是,则返回 true。[ -e $file ] 返回 true。
-f检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。[ -f $file ] 返回 true。
-d检测文件是否是目录,如果是,则返回 true。[ -d $file ] 返回 false。
-b检测文件是否是块设备文件,如果是,则返回 true。[ -b $file ] 返回 false。
-c检测文件是否是字符设备文件,如果是,则返回 true。[ -c $file ] 返回 false。
-S检测文件是否是Socket文件,如果是,则返回 true。[ -S $file ] 返回 false。
-p检测文件是否是有名管线,如果是,则返回 true。[ -p $file ] 返回 false。
-L检测文件是否是一个连接档,如果是,则返回 true。[ -L $file ] 返回 false。
-r检测文件是否可读,如果是,则返回 true。[ -r $file ] 返回 true。
-w检测文件是否可写,如果是,则返回 true。[ -w $file ] 返回 true。
-x检测文件是否可执行,如果是,则返回 true。[ -x $file ] 返回 true。
-u检测文件是否设置了 SUID 位,如果是,则返回 true。[ -u $file ] 返回 false。
-g检测文件是否设置了 SGID 位,如果是,则返回 true。[ -g $file ] 返回 false。
-k检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。[ -k $file ] 返回 false。
-s检测文件是否为空(文件大小是否大于0),不为空返回 true。[ -s $file ] 返回 true。

4 Bash的操作环境

4.1 路径与指令搜寻顺序

我们知道系统里面其实有不少的ls指令,那么哪一个指令会被用来执行呢?可以用type -a ls查看执行顺序,执行优先级顺序如下:

  • 以相对/绝对路径执行指令,例如/bin/ls./ls
  • 由alias找到该指令来执行
  • 由bash内建的(builtin) 指令来执行
  • 透过$PATH这个变量的顺序搜寻到的第一个指令来执行

4.2 Bash进站欢迎信息

通过tty1 ~ tty6登入bash时会有进站欢迎信息,这些信息定义和存放在/etc/issue中。

[yut@iZnq8v4wpstsagZ ~]$ cat /etc/issue
\S
Kernel \r on an \m

/etc/issue支持的变量有:

  • \d:本地端时间的日期
  • \l:显示第几个终端机介面
  • \m:显示硬体的等级(i386/i486/i586/i686…)
  • \n:显示主机的网路名称
  • \O:显示domain name
  • \r:作业系统的版本(相当于uname -r)
  • \t:显示本地端时间的时间
  • \S:作业系统的名称
  • \v:作业系统的版本

除了/etc/issue之外还有个/etc/issue.net,这个是提供给telnet 这个远端登入程式用的。当我们使用telnet连接到主机时,主机的登入画面就会显示/etc/issue.net而不是/etc/issue。也可以在/etc/motd中添加信息让登入系统时看见讯息。

4.3 bash 的环境设定档

4.3.1 login 与non-login shell

  • login shell:取得bash 时需要完整的登入流程的,就称为login shell。举例来说,你要由tty1 ~ tty6 登入,需要输入使用者的帐号与密码,此时取得的bash 就称为 login shell。
  • non-login shell:取得bash 介面的方法不需要重复登入的举动,举例来说,(1)你以X window 登入Linux 后, 再以X 的图形化界面启动终端机,此时那个终端界面并没有需要再次的输入帐号与密码,那个bash 的环境就称为non-login shell了。(2)你在原本的bash 环境下再次下达bash 这个指令,同样的也没有输入帐号密码, 那第二个bash (子程序) 也是non-login shell。

login shell与non-login shell两个取得bash时读取的设定档资料并不一样。

4.3.2 login shell读取设定档

login shell取得bash时读取的设定档有:

  • /etc/profile:这是系统整体的设定,最好不要修改这个档案。
  • ~/.bash_profile~/.bash_login~/.profile:属于使用者个人设定。这三个档案只有一个档案会被读取,读取的优先级按前面的顺序。

在这里插入图片描述
/etc/profile利用使用者的识别码(UID)来决定很多重要的变量,如PATH(根据UID决定是否含有sbin)、MAIL、USER、HOSTNAME、HISTSIZE、umask(root预设为022,一般用户预设为002)等。每个使用者登入取得bash时一定会读取这个设定档,/etc/profile还会通过.source去呼叫一些外部的设定档,有:

  • /etc/profile.d/*.sh:这个目录下副档名为.sh的档案,只要使用者拥有r的权限就会呼叫进来。这个目录底下的档案规范了bash 操作界面的颜色、 语系、ll 与ls 指令的命令别名、vi 的命令别名、which 的命令别名等。
  • /etc/locale.conf:这个档案是由/etc/profile.d/lang.sh呼叫进来的。决定bash预设使用何种语系。
  • /usr/share/bash-completion/completions/*:这个目录底下的档案是由/etc/profile.d/bash_completion.sh这个档案呼叫进来。这个包含了命令补齐、档名补齐和指令的选项/参数补齐功能等。

4.3.3 non-login shell读取设定档

non-login shell仅只会读取~/.bashrc设定档,root用户与一般用户的设定档内容会有些许不同,~/.bashrc会呼叫/etc/bashrc(Red Hat 系统特有的,其他不同的distributions可能会放置在不同的档名)设定档,这个设定档定义了:

  • 依据不同的UID规范出umask的值
  • 依据不同的UID 规范出提示字元(就是PS1 变数)
  • 呼叫/etc/profile.d/*.sh 的设定

4.3.4 其他相关设定档

  • /etc/man_db.conf:规范了使用 man的时候, man page的路径到哪里去寻找
  • ~/.bash_history:每次登入bash后,bash会先读取这个档案,将所有的历史指令读入记忆体,因此,当我们登入bash后就可以查知上次使用过哪些指令
  • ~/.bash_logout:当登出bash后,系统再做完一些操作指令后才离开,预设的情况下,登出时, bash只是帮我们清掉控制台的讯息

5 Shell Script

5.1 shell script编写规则

  • 指令的执行是从上而下、从左而右的分析与执行
  • 指令的下达就如同第四章内提到的:指令、选项与参数间的多个空白都会被忽略掉
  • 空白行也将被忽略掉,并且[tab] 按键所推开的空白同样视为空白键
  • 如果读取到一个Enter 符号(CR) ,就尝试开始执行该行(或该串) 命令
  • 至于如果一行的内容太多,则可以使用[Enter] 来延伸至下一行
  • #可做为注解,任何加在# 后面的资料将全部被视为注解文字而被忽略

常用特殊符号:

符号内容
#注解符号:常被使用在script 当中
\跳脱符号:将特殊字元或万用字元还原成一般字元
|管线(pipe):分隔两个管线命令的界定
;连续指令下达分隔符号:连续性命令的界定
~使用者的家目录
$取用变量的前置字元:亦即是变量之前需要加的变量取代值
&工作控制(job control):将指令变成后台运行
!逻辑运算意义上的非(not)
/目录符号:路径分隔的符号
>, >>资料流重导向:输出导向,分别是覆盖与累加
<, <<资料流重导向:输入导向
’ ’单引号,不具有变量置换的功能($ 变为纯文字)
" "双引号,具有变量置换的功能($ 可保留相关功能)
` `两个` 中间为可以先执行的指令,亦可使用$( )
( )在中间为子shell 的起始与结束
{ }在中间为命令区块的组合

5.2 简单的Shell练习

5.2.1 对谈式脚本

[yut@iZnq8v4wpstsagZ ~]$ vim showname.sh
#!/bin/bash
read -p "Please input your first name: " firstname       #提示使用者输入 
read -p "Please input your last name: " lastname         #提示使用者输入 
echo -e "\nYour full name is: ${firstname} ${lastname}"  #结果由萤幕输出
[yut@iZnq8v4wpstsagZ ~]$ sh showname.sh 				 #执行脚本
Please input your first name: yu
Please input your last name: tao

Your full name is: yu tao

5.2.2 随日期变化:利用date进行档案建立

某些档案需要每天去建立,如备份档案,可以通过日期来命名档案以便于管理档案

[yut@iZnq8v4wpstsagZ ~]$ vim create_date_file.sh
#!/bin/bash
# 1.让使用者输入档案名称,并取得fileuser这个变数; 
echo -e "I will use 'touch' command to create 3 files."  #纯粹显示资讯 
read -p "Please input your filename: " fileuser          #提示使用者输入

# 2.为了避免使用者随意按Enter ,利用变量功能分析档名是否有设定?
filename=${fileuser:-"filename"}                         #开始判断有否设定档名

# 3.开始利用date指令来取得所需要的档名了; 
date1=$(date --date='2 days ago' +%Y%m%d)                #前两天的日期 
date2=$(date - -date='1 days ago' +%Y%m%d)               #前一天的日期 
date3=$(date +%Y%m%d)                                    #今天的日期 
file1=${filename}${date1}                                #底下三行在设定档名
file2=${filename}${date2}
file3=${filename}${date3}

# 4.将档名建立吧!
touch "${file1}"                                         #底下三行在建立档案
touch "${file2}"
touch "${file3}"

5.2.3 Script执行方式差异(source script、sh script、./script)

执行一个shell script,可以使用source scriptsh script(或bash)、./script(绝对/相对路径)三种方式去执行,后二种方式会使用一个新的bash环境来执行脚本中的指令,而第一种方式会在当前的bash环境中执行脚本中的指令,因此它们的差异在于是否可以使用父程序的自定义局部变量。

sh指令还有语法检查等功能:

#sh [-nvx] scripts.sh 

#-n :不执行script,仅查询语法的问题
#-v :在执行sccript 前,先将scripts 的内容输出到控制台
#-x :将使用到的script内容显示到控制台
[yut@iZnq8v4wpstsagZ ~]$ sh -n create_date_file.sh
[yut@iZnq8v4wpstsagZ ~]$ sh -x create_date_file.sh

5.3 Shell script 的预设变量($0, $1…)

我们可以在执行 Shell 脚本时,向脚本传递参数,脚本内获取参数的格式为:${字符}
例:

[yut@iZnq8v4wpstsagZ ~]$ vim params.sh
#!/bin/bash
echo "参数个数:${#}"
echo "参数列表:${@}"
echo "参数列表字符串:${*}"
echo "第二个参数值:${2}"
[yut@iZnq8v4wpstsagZ ~]$ sh params.sh one two three four
参数个数:4
参数列表:one two three four
参数列表字符串:one two three four
第二个参数值:two

shell支持的几种字符:

参数说明
$#传递到脚本的参数个数
$@所有参数的数组
$*所有参数用空格间隔的字符串
$nn是数字,9以上必须使用${n},代表第n个参数
$$脚本运行的当前进程ID号
$!后台运行的最后一个进程的ID号
$-显示Shell使用的当前选项,与set命令功能相同
$?显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。

输入的参数实际上会组成一个数组,每个参数对应一个元素,可以使用shift n指令从数组的头部移除n个元素:

#从数组头部移除一个元素
shift 1

5.4 流程控制

5.4.1 条件判断式

5.4.1.1 if

语法:

if [ condition ];then
	command1
elif[ condition ];then
	command2
else
	command3
fi

注意:if流程控制分支不可为空,如else分支没有语句执行,就不要写这个else。

示例:

[yut@iZnq8v4wpstsagZ ~]$ vim ans_yn.sh
#!/bin/bash
read -p "Please input (Y/N): " yn

if [ "${yn}" == "Y" ] || [ "${yn}" == "y" ]; then
	echo "OK, continue"
elif [ "${yn}" == "N" ] || [ "${yn}" == "n" ]; then
	echo "Oh, interrupt!"
else
	echo "I don't know what your choice is"
fi

以下是一个检测服务是否在运行的脚本示例:

[yut@iZnq8v4wpstsagZ ~]$ vim netstat.sh
#!/bin/bash
testfile=./netstat_checking.txt
test -e netstat_checking.txt || touch netstat_checking.txt		#检查netstat_checking.txt是否存在,不存在则创建
netstat -tuln > ${testfile}           							#先转存资料到记忆体当中!不用一直执行netstat 
testing=$(grep ":80 " ${testfile})    							#侦测看port 80在否?
if [ "${testing}" != "" ]; then
    echo "WWW is running in your system."
fi
testing=$(grep ":22 " ${testfile})    							#侦测看port 22在否?
if [ "${testing}" != "" ]; then
    echo "SSH is running in your system."
fi
testing=$(grep ":21 " ${testfile})    							#侦测看port 21在否?
if [ "${testing}" != "" ]; then
    echo "FTP is running in your system."
fi
testing=$(grep ":25 " ${testfile})    							#侦测看port 25在否?
if [ "${testing}" != "" ]; then
    echo "Mail is running in your system."
fi
[yut@iZnq8v4wpstsagZ ~]$ sh netstat.sh 
WWW is running in your system.
SSH is running in your system.
Mail is running in your system.

5.4.1.2 case

我们执行一些指令的时候,可以携带参数已达到实现不同的功能。早期系统的很多服务的启动scripts都是使用携带参数这种写法的(CentOS 6.x以前)。虽然CentOS 7已经使用systemd,不过仍有数个服务是放在/etc/init.d/目录下如/etc/init.d/netword(可以去翻看下脚本):

[yut@iZnq8v4wpstsagZ ~]$ /etc/init.d/network restart

语法:

case $variable in
"val1")
	command1
;;
"val2")
	command2
;;
*)
	command3
;;
esac

模拟network的简单示例:

[yut@iZnq8v4wpstsagZ ~]$ vim show123.sh
#!/bin/bash
case ${1} in                              
  "")							#没输入参数
    echo "You MUST input parameters, ex> {${0} someword}"
    ;;
  "one")
    echo "Your choice is ONE"
    ;;
  "two")
    echo "Your choice is TWO"
    ;;
  "three")
    echo "Your choice is THREE"
    ;;
  *)
    echo "Usage ${0} {one|two|three}"
    ;;
esac

5.4.2 循环

5.4.2.1 for循环

第一种语法:

for var in con1 con2 con3 ...
do
	command
done

判断1~100ip的连通性示例:

[yut@iZnq8v4wpstsagZ ~]$ vim pingip.sh
#!/bin/bash
network="192.168.1"               #定义网段
for sitenu in $(seq 1 100)        #seq为sequence(连续)的缩写
do
		# 判断ping的回传值是正确的还是失败的
        ping -c 1 -w 1 ${network}.${sitenu} &> /dev/null && result=0 || result=1
		# 开始显示结果是正确的启动(UP) 还是错误的没有连通(DOWN)
        if [ "${result}" == 0 ]; then
                echo "Server ${network}.${sitenu} is UP."
        else
                echo "Server ${network}.${sitenu} is DOWN."
        fi
done

第二种语法:

for ((初始值;限制值;执行步阶))
do
	command
done
  • 初始值:某个变量在循环中的起始值。直接以类似i=1设定好
  • 限制值:当变量的值在这个限制值的范围内,就继续进行循环。例如i<=100
  • 执行步阶:每作一次循环时,变量的变化量。例如i=i+1。

示例:

[yut@iZnq8v4wpstsagZ ~]$ vim sum.sh
#!/bin/bash
s=0
nu=5
for (( i=1; i<=${nu}; i=i+1 ))
do
	s=$((${s}+${i}))
done
echo "The result of '1+2+3+...+${nu}' is ==> ${s}"
5.4.2.2 while与until循环

语法:

#while语法
while [ condition ]
do
    command
done

#until语法
until [ condition ]
do
	command
done

while当condition条件不成立时,就终止循环,而until与while相反
示例:

[yut@iZnq8v4wpstsagZ ~]$ vim yes_to_stop.sh
#!/bin/bash 
#until [ "${yn}" != "yes" -a "${yn}" != "YES" ]
while [ "${yn}" != "yes" -a "${yn}" != "YES" ]
do
	read -p "Please input yes/YES to stop this program: " yn
done
echo "OK! you input the correct answer."
5.4.2.3 无限循环

无限循环语法格式:

while :
do
    command
done

#或者
while true
do
    command
done

#或者
for (( ; ; ))
do
    command
done
5.4.2.4 跳出循环

在循环过程中,有时候需要在未达到循环结束条件时强制跳出循环,Shell使用两个命令来实现该功能:break和continue。

break

break命令允许跳出所有循环(终止执行后面的所有循环)。例:

[yut@iZnq8v4wpstsagZ ~]$ vim break.sh
#!/bin/bash
while :
do
    echo -n "输入 1 到 5 之间的数字:"
    read aNum
    case $aNum in
        1|2|3|4|5) echo "你输入的数字为 $aNum!"
        ;;
        *) echo "你输入的数字不是 1 到 5 之间的! 游戏结束"
            break
        ;;
    esac
done
continue

continue命令与break命令类似,只有一点差别,它不会跳出所有循环,仅仅跳出当前循环。例:

[yut@iZnq8v4wpstsagZ ~]$ vim continue.sh
#!/bin/bash
while :
do
    echo -n "输入 1 到 5 之间的数字: "
    read aNum
    case $aNum in
        1|2|3|4|5) echo "你输入的数字为 $aNum!"
        ;;
        *) echo "你输入的数字不是 1 到 5 之间的!"
            continue
            echo "游戏结束"
        ;;
    esac
done

5.5 function

Linux shell 可以用户定义函数,然后在shell脚本中可以随便调用,并且可以简化我们很多的程式码,与其它语言中函数功能相似。
语法:

function funname [()]{
	action
	
    [return int]
}

  1. 可以带function fun() 定义,也可以直接fun() 定义,不带任何参数。
  2. 参数返回,可以显示加return返回,如果不加,将以最后一条命令运行结果,作为返回值,使用$?获取。 return后跟数值n(0-255)。

示例:

#!/bin/bash
function printit(){
    echo -n "Your choice is ${1},transfer:"      	#加上-n可以不断行继续在同一行显示
}

echo "This program will print your selection !"
case ${1} in
  "one")
    printit ${1}; echo ${1} | tr 'az' 'AZ'
    ;;
  "two")
    printit ${1}; echo ${1} | tr 'az' 'A-Z'
    ;;
  "three")
    printit ${1}; echo ${1} | tr 'a-z' 'A-Z'
    ;;
  *)
    echo "Usage ${0} {one|two|three}"
    ;;
esac

function也是拥有内建变量的,他的内建变量与shell script很类似,函数名称代表是$0 ,而后续接的变数也是以$1, $2…来取代的。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值