Shell 基础
脚本编程语言通常是解释型(interpreted)的。这类程序的执行,是由解释器(interpreter)读入程序代码,并将其转换成内部的形式,再执行。
脚本编程语言的例子有awk、Perl、Python、Ruby与Shell。
一个简单的例子:
$ who
marvin tty7 2017-09-26 20:36 (:0)
利用管道连接两个程序:
$ who | wc -l
1
另一个例子:
$ cat > nusers #建立文件,使用cat复制终端的输入
who | wc -l #程序的内容
^D #Ctrl-D表示end-of-file
$ chmod +x nusers #让文件拥有执行的权限
$ ./nusers #执行测试
1 #结果输出
当一个文件中开头的两个字符是#!
时,内核会扫描该行其余的部分,看是否存在可用来执行程序的解释器的完整路径。(中间如果出现任何空白符号都会略过。)此外,内核会扫描是否有一个选项要传递给解释器。内核会以指定的选项来引用解释器再搭配命令行的其他部分。
$ cat nusers
#! /bin/bash
who | wc -l
Shell的基本元素
命令和参数
$ cd work ; ls -l whizprog.c
以上的例子展现了UNIX命令行的原理。首先,以空白(space键或tab键)隔开命令行中各个组成部分。其次,命令名称是命令行的第一个项目。通常后面会跟着选项(option),任何额外的参数(augment)都会放在选项之后。第三,选项的开头是一个破折号(或减号),后面接着一个字母。不需要参数的选项可以合并。以两个破折号(–)来表示选项开始的用法,源自System V,不过已被纳入POSIX标准。
分号(;)用来分隔同一行里的多条命令。Shell会依次执行这些命令。如果使用的是&符号而不是分号,则Shell将在后台执行前面的命令,这意味着,Shell不用等待该命令完成,就可以继续执行下一跳命令。
Shell识别三种基本命令:内建命令、Shell函数和外部命令。
变量
Shell变量名称的开头是一个字母或下划线符号,后面可以接着任意长度的字母、数字或下划线符号。Shell变量可用来保存字符串值,所能保存的字符数没有限制。
$ myvar=this_is_a_long_string_that_does_not_mean_mach #分配变量值
$ echo $myvar #打印变量值
this_is_a_long_string_that_does_not_mean_mach
变量赋值的方式是:先写变量名,紧接着=字符,最后是新值,中间完全没有任何空格。当想取出变量的值时,要在变量的名称前面加上$字符。当所赋予的值内含空格时,需加上引号。
1、变量的命名
bash shell中变量的命名规则和C语言相同,必须是由英文字母、数字及下划线组成,第一个字符必须是字母或下划线,变量的长度没有限制,但英文字母区分大小写。虽然,bash shell中使用变量时不需要声明,但还是提倡对一些重要的变量进行声明、添加注释,以便阅读和维护。声明或创建一个变量之后,它的作用域是当前shell,子shell无法获取父shell中定义的变量,除非该变量时环境变量。
2、设定变量
在bash shell中要设置某个变量的值是很容易的,只需要按照:
变量名称=值
的方式即可改变某个变量的值,需要注意的是等号的两边是不能有空格的,若值中含有空格的话,需要用引号括起来。
3、获取变量值
要获取某个变量的值只需要在该变量的名称前面加上
# echo $PATH
# echo ${PATH}
4、取消与清空变量
当你不再需要某个变量时,你可能想取消该变量,即将该变量从当前名字空间中删除并释放该变量所占用的内存。在bash shell中可以用unset命令来取消某个变量。用法如下:
unset 变量名称 或 unset -v 变量名称
-v表示取消变量,unset除了可以用来取消变量外,还可以用来取消函数,用unset来取消函数时,用法如下:
unset -f 函数名称
使用unset以后,变量就不复存在了,这可能并不是你想要的,你可能只是想将清除该变量中的值,使其为null,即清空变量,清空变量的操作如下:
变量名称=
5、环境变量
只有当一个变量成为环境变量时,它才能为子shell所用,为了使一个变量成为环境变量,需要使用export命令,具体如下:
变量名称="xxxx"
export 变量名称
或
export 变量名称="xxxx"
除了使用export之后,还可以在声明的时候就将变量指定为环境变量,如下:
declare -x 变量名称
6、bash的内置变量
除了环境变量和用户自定义的变量之外,bash shell中还会用到很多的内置变量,下面介绍一些常用的内置变量。
BASH
– bash的完整路径,通常是/bin/bash
BASH_VERSION
– bash的版本
BASH_ENV
– 在非交互模式下,会先检查$BASH_ENV是否有指定的启动文件,如果有则先执行它
ENV
– 与BASH_ENV类似,不过是在POSIX模式下,会先检查$ENV是否有指定的启动文件,如果有则先执行它
CDPATH
– cd命令的搜索路径
PATH
– 命令的搜索路径
EUID
– 有效的用户id
FUNCNAME
– 在函数执行期间,即为函数的自身的名称
HOSTNAME
– 主机名
HOSTTYPE
– 主机类型,如i386
OSTYPE
– 执行bash的操作系统类型,如linux-gnu
HOME
– 用户主目录
IFS
– 默认的字段分隔符
OPTARG
– 使用getopts处理选项时,取得的选项的参数
OPTIND
– 使用getopts处理选项时,选项的索引值
OPTERR
– 若将OPTERR设置为1,则getopts发生错误,输出错误信息
$1~$n
– 位置参数,即传入程序或函数的参数,
$*
– 所有的位置参数,并将其看成一个字符串,如”test.sh abc 123“,则$*为”abc 123“
$@
– 所有的位置参数,并将其看成一个字符串数组,如”test.sh abc 123“,则$*为”abc 123“
$#
– 位置参数的个数
$?
– 上一条命令执行结束后的返回值
$$
– 当前bash shell的进程号
$!
– 上一个后台程序的进程号
7、调整变量的属性
declare命令不仅可以用来声明变量,还可以用来调整变量的属性,具体用法如下:
-p 显示变量的属性
-a 变量是一个数组
-i 变量是一个整数
-r 变量为只读的
-x 变量为环境变量
简单的echo输出
原始的echo
命令只会将参数打印到标准输出,参数之间以一个空格隔开,并以换行符号(newline)结尾。
echo转义序列
printf输出
printf
命令的完整语法分为两部分:
printf format-stirng [arguments ... ]
第一部分是一个字符串,用来描述输出的排列方式,最好为此字符串加上引号。此字符串包含了按字面显示的字符(characters to be printed literally)以及格式声明(format specifications),后者是特殊的占位符(placeholders),用来描述如何显示相应的参数(argument)。
第二部分是与格式声明相对应的参数列表(argument list)。格式声明分成两部分:百分比符号(%)和指示符号(specifier)。最常用的格式指示符(format specifier)有两个,%s用于字符串,%d用于十进制整数。
$ printf "The first program always prints '%s, %s'
" Hello world
The first program always prints 'Hello, world'
bash shell启动配置文件
Bash有4种运行模式,分别是:
1)交互模式:即bash在终端中等待你输入一条命令,然后对这条命令进行解释执行,最后输出结果的执行过程;如当你想了解当前目录下有哪些文件时,你可以打开一个终端,输入ls命令即可。
2)非交互模式:即通过执行shell脚本的方式使用bash,你可以将你经常要用到的一些命令写出脚本,然后每次调用该shell脚本,这样就可以省去每次都输入同样的命令的麻烦。此外,通过shell脚本可以完成很多复杂繁琐的事情,大大的提高自动化的程度。
3)POSIX模式:即以“bash –posix”方式来运行bash。在POSIX模式下,bash与POSIX标准兼容,此时bash会检查ENV变量的内容,若有定义,就执行该变量所定义的配置文件的内容。
4)受限模式:即以“bash -r”方式来运行bash,在该模式下,bash的功能受到许多限制,如:不能使用cd命令、不能设定或取消环境变量、不能做输入输出的重定向等操作。
在不同的模式下,bash调用不同的启动配置文件:
登录(login):首先执行/etc/profile,接着bash检查用户主目录的配置文件,依次检查.bash_profile、.bash_login、.profile等配置文件是否存在,如果存在则加载其中的配置。
注销(logout):注销时,bash检查主目录是否有.bash_logout,如有,则读取并执行它。
- 执行新shell:执行新shell是指登录以后,创建新的shell,这时根据bash的执行模式会加载不同的配置文件:
- 交互模式:在该模式下,bash首先会读取并执行/etc/bash.bashrc,然后读取并执行.bashrc
- 非交互模式:在非交互模式下,它会检查BASH_ENV变量的内容,若该变量有定义,则执行该变量所定义的启动文件的内容
基本的I/O重定向
以
<
改变标准输入program < file
可将program
的标准输入改为file
:tr -d ' ' < dos-file.txt ...
以
>
改变标准输出program > file
可将program
的标准输出修改为file
:tr -d ' ' < dos-file.txt > UNIX-file.txt
这条命令会先以
tr
件dos-file.txt
里的ASCII carriage-return(回车)
删除,再将转换成的数据输出到UNIX-file.txt
。dos-file.txt
里的原始数据不会有变化。>
重定向符(redirector)在目的文件不存在时,会新建一个。然而,如果目的文件已存在,他就会覆盖掉,原本的数据都会丢失。
以
>>
附加到文件program >> file
可将program
的标准输出附加到file
的结尾处。如同
>
,目的文件不存在时,会新建一个。然而,如果目的文件已存在,它不会直接覆盖掉文件,而是将程序产生的数据附加到文件结尾处。for f in dos-file*.txt do tr -d ' ' < $f >> big-UNIX-file.txt done
以
|
建立管道program1 | program2
可将program1
的标准输出修改为program2
的标准输入。虽然
<
与>
可将输入与输出连接到文件,不过管道(pipeline)可以把两个以上执行中的程序衔接在一起。第一个程序的标准输出变为第二个程序的标准输入。这样做可以加快程序的执行速度。tr -d ' ' < dos-file.txt | sort > UNIX-file.txt # 这条管道会先删除输入文件内的回车字符,在完成数据的排序后,将结果输出到目的文件
tr命令
语法
shell
tr [ options ] source-char-list replace-char-list
用途
- 转换字符。
常用选项
-c
取
source-char-list
的反义,tr要转换的字符,变成未列在source-char-list
中的字符。-C
与
-c
相似,但所处理的是字符,而非二进制的字节值。根据POSIX标准的定义,
-c
处理的是二进制字节值,而-C
处理的是现行locale所定义的字符。-d
自标准输入删除
source-char-list
里所列的字符。-s
浓缩重复的字符。如果标准输入中连续重复出现
source-char-list
里所列的字符,则将其浓缩成一个。
行为模式
- 如同过滤器:自标准输入读取字符,再将结果写到标准输出。任何输入字符只要出现在
source-char-lsit
中,就会置换成replace-char-list
里相应的字符。
- 如同过滤器:自标准输入读取字符,再将结果写到标准输出。任何输入字符只要出现在
特殊文件
/dev/null
第一个文件/dev/null
就是熟知的位桶(bit bucket)。传送到此文件的数据都会被系统过滤掉。也就是说,当程序将数据写到此处文件时,会认为它已经成功完成写入数据的操作,但实际上什么事都没做。
例如。测试一个文件是否包含某个模式(pattern)
if grep pattern myfile >> /dev/null
then
...#找到模式时
else
...#找不到模式时
fi
相应地,读取dev
ull
会立即返回文件结束符号(end-of-file)。
/dev/tty
另一个特殊文件为/dev/tty
。当程序打开此文件时,UNIX会自动将它重定向到一个终端[一个实体的控制台(console)或串行端口(serial port),也可能是一个通过网络与窗口登录的伪终端(pseudo terminal)]再与程序结合。
这在程序必须读取人工输入时(例如密码)特别有用。例如:
printf "enter new password: " #提示输入
stty -echo #关闭自动打印输入字符的功能
read pass < /dev/tty #读取密码
printf "enter again: "
read pass2 < /dev/tty
stty echo #打开自动打印输入字符的功能
stty
(set tty)命令用来控制终端(或窗口)的各种设置。
基本命令查找
Shell会沿着查找路径$PATH
来寻找命令。$path
是一个以冒号分隔的目录列表,可以在列表所指定的目录下找到所要执行的命令。所找到的命令可能是编译后的可执行文件。也可能是Shell脚本。
默认路径因系统而异,不过至少包含/bin
和/usr/bin
,也许还包含存放X Windows程序的/usr/X11R6/bin
。以及供本地系统管理人员安装程序的/usr/local/bin
,例如:
$ echo $PATH
/home/marvin/bin:/home/marvin/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
如果要编写自己的脚本,最好准备自己的bin目录来存放它们,并且让Shell能够自动找到它们。例如:
$ cd #切换到home目录
$ mkdir bin
$ mv nusers bin
$ PATH=$PATH:$HOME/bin #将个人的bin目录附加到PATH
要让修改永久生效,在.profile
文件中把你的bin目录加入$PATH
,而每次登陆时Shell都会读取.profile
文件。
$PATH里的空项目(empty component)表示当前目录。例如:
PATH=:/bin/:/usr/bin:/usr/local/bin #先找当前目录
PATH=/bin/:/usr/bin:/usr/local/bin: #最后找当前目录
PATH=:/bin/:/usr/bin::/usr/local/bin #当前目录居中
最好的做法是在
$PATH
中使用点号(dot)
访问Shell脚本的参数
1、位置参数
由系统提供的参数称为位置参数。位置参数的值可以用
所谓位置参数(positional parameters)指的是Shell脚本的命令行参数(command-line arguments)。在Shell函数里,它们同时也可以是函数的参数。例如:
echo first arg is $1
echo tenth arg is $(10)
$ cat > finduser #建立新文件
#! /bin/bash
#finduser 查看第一个参数所指定的用户是否登录
who | grep $1
^D
$ chmod +x finduser
$ $ ./finduser marvin
marvin tty2 2017-10-08 14:39 (:1)
2、内部参数
上述过程中的
$# – 传递给程序的总的参数数目
$? – 上一个代码或者CentOS shell程序在shell中退出的情况,如果正常退出则返回0,反之为非0值。
$* – 传递给程序的所有参数组成的字符串。
字符串比较
作用:测试字符串是否相等、长度是否为零,字符串是否为NULL(注:bash区分零长度字符串和空字符串)
常用的字符穿操作符有:
= 比较两个字符串是否相同,同则为“是”
!= 比较两个字符串是否相同,不同则为“是”
-n 比较字符串长度是否大于零,如果大于零则为“是”
-z 比较字符串的穿度是否等于零,如果等于则为“是”
数字比较
这里区别于其他编程语言,shell中不使用>、<、>=类似的符号来表达大小的比较,而是用整数式来表示这些。
-eq 相等
-ge 大于等于
-le 小于等于
-ne 不等于
-gt 大于
-lt 小于
逻辑操作
! 反:与一个逻辑值相反的逻辑值
-a 与:两个逻辑值为“是”返回值才为“是”,反之为“否”
-o 或:两个逻辑值有一个为“是”,返回值就为“是”
文件操作
文件测试表达式通常是为了测试文件的信息,一般由脚本来决定文件是否应该备份、复制或删除。由于test关于文件的操作符有很多,我们只列举一些常用的。
-d 对象存在且为目录返回值为“是”
-f 对象存在且为文件返回值为“是”
-L 对象存在且为符号连接返回值为“是”
-r 对象存在且可读则返回值为“是”
-s 对象存在且长度非零则返回值为“是”
-w 对象存在且可写则返回值为“是”
-x 对象存在且可执行则返回值为“是”
执行跟踪
将执行跟踪(execution tracing)的功能打开,这会使得Shell显示每个被执行到的额命令,并在前面加上”+ “:一个加号后面跟着一个空格。(可以通过给Shell变量PS4赋一个新值来改变打印方式)
例如:
$ sh -x nusers #开启执行跟踪功能
+ who #被跟踪的命令
+ wc -l
1 #实际的输出
也可以在脚本里,用set -x
命令将执行跟踪功能打开,然后set +x
命令关闭它。
国际化和本地化
internationalizatioin
localization