快速了解和学习Unix/Linux下Shell(一)

Tags: linux shell bash

一、Shell简介

Shell源于UnixShell,本身是一个用C语言编写的程序,它是用户使用Uninx/Linux的桥梁,是用户与Unix/Linux交互主要的交互界面和工具,学习使用Shell首先需要具备一定的Unix/Lunix基础。Shell虽然是Unix的第一个脚本语言但是它一直被忽略,是一个不受重视的脚本语言,不过Shell是非常优秀且独具特色的一种脚本语言,它结合了延展性与效率,并且在Unix/Linux的发展过程中不断的被改良,使它多年来能与很多新兴的的脚本语言保持抗衡。Shell需要依赖其他应用程序才能完成大部分的工作,这或许是它的缺陷或许不是,但它长处是:简洁的标记方式,而且比C语言等编译性语言编写和修改更加容易和快速。

Shell有两种执行命令的方式:

  • 交互式(Interactive):解释执行用户的命令,用户输入一条命令,Shell就解释执行一条。

  • 批处理(Batch):用户事先写一个Shell脚本(Script),包含有很多条命令Shell可以一次执行完这些命令。

Shell脚本和编程语言很相似,也有变量和流程控制语句,但Shell脚本是解释执行语言不需要编译,Shell解释程序从脚本中逐行读取并命令并执行,这相当于系统帮助用户把脚本中的命令一行一行敲到Shell提示符下执行。

二、Shell解释器

Unix/Linux上常见的Shell解释器有bash、sh、csh、ksh等,习惯上把它们称作统称为Shell。我们常说有多少种Shell,其实说的是有多少种Shell脚本解释器。

1.bash

bash是大多数Linux发行版默认使用的shell解释环境(这篇文档的所有例子基于bash)。bash由Brian Fox和Chet Ramey共同开发完成,其名称是由BourneAgain Shell的缩写而来,一共有40个内部命令。
大多数Linux发行版使用bash作为默认的shell是因为它有下列特色:

  • 可以使用类似DOS下面的doskey的功能,用方向键查阅和快速输入并修改命令。

  • 自动通过查找匹配的方式可以快速给出以某字符串开头的命令(按tab键)。

  • 包含了丰富的帮助文档,你只要在提示符下面键入help就可以得到相关的帮助。

bash基于Bourne shell,吸收了C shell和Korn shell的一些特性。bash完全兼容sh,用sh写的脚本可以不加修改的在bash中执行。

2.sh

sh由Steve Bourne开发,是Bourne Shell的缩写,sh是Unix环境下默认的shell解释器。

3.ash

ash shell由Kenneth Almquist编写,是Linux众多shell解释其中中占用系统资源最少的一个,它只包含24个内部命令,因而使用起来很不方便。

4.csh

csh 是Linux shell解释器中比较大的一种,它由以William Joy为代表的共计47位作者共同编写,共有52个内部命令。csh其实是/bin/tcsh的一个文件链接,也实际上csh就是tcsh。

5.ksh

ksh(Korn shell)由Eric Gisin编写的开源shell,共有42条内部命令。ksh最大的优点是几乎和商业发行版的ksh完全兼容,这样就可以在开源系统环境下尝试商业版本的特性了。

三、语言的执行方式

1.编译型语言

很多传统的程序设计语言,例如Fortran、Ada、Pascal、C、C++和Java,都是编译型语言。这类语言需要预先将我们写好的源代码(source code)转换成目标代码(object code),这个过程被称作“编译”。
运行程序时CPU(或虚拟机)直接读取目标代码(object code)并执行,编译型语言的有点是

    由于编译后的目标代码(object code)非常接近计算机底层代码,因此执行效率很非常高。
但是,由于编译型语言需要编写、编译、执行等过程,且很多语言面向的是系统底层开发,因此往往实现一个简单的功能时需要大量的代码。另外,由于编译后在没有编译环境的情况下无法对程序进行修改,以适应新操作系统环境,所以编译型语言的可重用性不高。

2.解释型语言

解释型语言也被称作“脚本语言”。解释型语言在执行时是由解释器(interpreter)读取源代码(source code)并即时编译成目标代码(object code),再由计算机运行。因为每次执行程序都需要有编译过程,因此执行效率一般不如编译型语言。
使用脚本编程语言的好处是,它们多半运行在比编译型语言还高的层级,能够轻易处理文件与目录之类的对象;缺点是它们的效率通常不如编译型语言。不过权衡之 下,通常使用脚本编程还是值得的:花一个小时写成的简单脚本,同样的功能用C或C++来编写实现,可能需要两天,而且一般来说,脚本执行的速度已经够快 了,快到足以让人忽略它性能上的问题。脚本编程语言的例子有awk、Perl、Python、Ruby与Shell。

四、何时需要用到Shell?

因为Shell是各UNIX系统通用解释环境,并且POSIX也对shell提出了相关标准。因此,Shell脚本实际上是一次编写到处运行。

Shell脚本的优势:

  • 简单:Shell是一个高级语言;通过它,你可以用简介优雅的代码实现复杂的操作。

  • 可移植:在POSIX的规范下,可以做到脚本一次编写到处运行。

  • 容易编写:好写,好改,只需要一个最简单的文本编辑器(vi,emacs...)就够了。

但是,考虑到Shell的环境限制和效率问题,下列情况一般不使用Shell:

  • 资源密集型的任务,尤其在需要考虑效率时(比如,排序,hash等等)。

  • 需要处理大任务的数学操作,尤其是浮点运算,精确运算,或者复杂的算术运算(这种情况一般使用C++或FORTRAN 来处理)。

  • 有跨平台(操作系统)移植需求(一般使用C 或Java)。

  • 复杂的应用,在必须使用结构化编程的时候(需要变量的类型检查,函数原型,等等)。

  • 对于影响系统全局性的关键任务应用。

  • 对于安全有很高要求的任务,比如你需要一个健壮的系统来防止入侵、破解、恶意破坏等等。

  • 项目由连串的依赖的各个部分组成。

  • 需要大规模的文件操作。

  • 需要多维数组的支持。

  • 需要数据结构的支持,比如链表或数等数据结构。

  • 需要产生或操作图形化界面 GUI。

  • 需要直接操作系统硬件。

  • 需要 I/O 或socket 接口。

  • 需要使用库或者遗留下来的老代码的接口。

  • 私人的、闭源的应用(shell 脚本把代码就放在文本文件中,全世界都能看到)。

如果你的业务需求符合上边的任意一条,那么就考虑一下更强大的语言吧——或许是Perl、Tcl、Python、Ruby——或者是更高层次的编译语言比如C/C++、Java。即使如此,在开发过程中使用shell作为原型工具,也是非常有用的。

五、写第一个Shell

打开文本编辑器,新建一个扩展名为sh的文件(sh代表shell,当然其他扩展名也行,只要不会混淆),输入下列代码:

#!/bin/bash
echo "Hello World !"

“#!” 是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种Shell,echo命令用于向窗口输出文本。

1. 运行Shell

有两种方式来执行你刚编写的shell。

1.1 作为可执行程序

将上面的代码保存为test.sh,并 cd 到相应目录:

chmod +x ./test.sh  #使脚本具有执行权限
./test.sh           #执行脚本

注意,一定要写成./test.sh,而不是test.sh。运行其它程序也一样,直接写test.sh,linux系统会根据PATH环境变量里寻找相应名字的应用程序或可执行shell脚本,而默认的PATH环境变量只有/bin, /sbin, /usr/bin,/usr/sbin等几个路径,你的当前目录通常不在PATH环境变量中,所以写成test.sh是会提示找不到命令,而用./test.sh相当于告诉系统我所要执行的命令就在当前目录中。

注意:通过这种方式运行shell脚本,第一行一定要写#!/bin/bash,好让系统查找到正确的解释器。

这里的"系统",其实就是shell这个应用程序(想象一下Windows Explorer),但我故意写成系统,是方便理解,既然这个系统就是指shell,那么一个使用/bin/sh作为解释器的脚本是不是可以省去第一行呢?是的。

1.2 作为解释器参数

这种运行方式是,直接运行解释器,其参数就是shell脚本的文件名,如:

/bin/sh test.sh    
/bin/php test.php

这种方式运行的脚本,不需要在第一行指定解释器信息,写了也没用。

再看一个例子。下面的脚本使用 read 命令从 stdin 获取输入并赋值给 PERSON 变量,最后在 stdout 上输出:

#!/bin/bash
# Author : 4byte.cn
# Copyright (c) http://www.4byte.cn
# Script follows here:
echo "What is your name?"
read PERSON
echo "Hello, $PERSON"

运行脚本:

chmod +x ./test.sh    
$./test.sh
What is your name?
4byte.cn
Hello, 4byte.cn
$

六、变量

Shell支持自定义变量。

1.定义变量

定义变量时变量名不加美元符号($),如:

variableName="value"

注意,变量名和等号之间不能有空格,这可能和你熟悉的所有编程语言都不一样。同时,变量名的命名须遵循如下规则:

  • 首个字符必须为字母(a-z,A-Z)。

  • 中间不能有空格,可以使用下划线(_)。

  • 不能使用标点符号。

  • 不能使用bash里的关键字(可用help命令查看保留关键字)。


如:

url="
num=100

2.使用变量

只要在变量名前面加美元符号($)即可使用变量,如:

your_name="4byte.cn"
echo $your_name
echo ${your_name}

变量名外面的花括号是可选的,加花括号是为了帮助解释器识别变量定义边界,比如下面这种情况:

for skill in Ada Coffe Action Java
do
   echo "I am good at ${skill}Script"
done

如果不给skill变量加花括号,写成echo "I am good at $skillScript",解释器就会把$skillScript当成一个变量(其值为空),代码执行结果就不是我们期望的样子了。推荐给所有变量加上花括号,这是个好的编程习惯。

3.变量重新赋值

已定义的变量可以重新赋值,如:

url="
echo ${url}
url=""
echo ${url}

4.只读变量

使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变。

下面的例子是错误的,因为尝试更改只读变量,结果报错:

#!/bin/bash
url="http://www.4byte.cn/question/tags/linux" 
readonly url
url="http://www.4byte.cn/learning/tags/linux"

运行脚本,结果如下:

/bin/sh: NAME: This variable is read only.

5.删除变量

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

unset variable_name

变量被删除后不能再次使用;unset 命令不能删除只读变量。下面例子不会有任何输出:

#!/bin/sh
url=" 
unset url
echo $url

6.变量类型

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

  • 局部变量

局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。

  • 环境变量

所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。

  • shell变量

shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行

七、特殊用途变量:Shell $0, $#, $*, $@, $?, $$和命令行参数

1. 特殊用途变量

Shell中包含有特殊含义的变量,这样的变量被称为特殊变量。

例如,$ 表示当前Shell进程的PID,看下面的代码:

$echo $$

运行结果

32282
        特殊用途变量列表
变量含义
$0当前脚本的文件名
$n传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是$1,第二个参数是$2。
$#传递给脚本或函数的参数个数。
$*传递给脚本或函数的所有参数。
$@传递给脚本或函数的所有参数。被双引号(" ")包含时,与 $* 稍有不同,下面将会讲到。
$?上个命令的退出状态,或函数的返回值。
$$当前Shell进程ID。对于 Shell 脚本,就是这些脚本所在的进程ID。

2. 从Shell获取命令行参

运行脚本时传递给脚本的参数称为命令行参数。命令行参数用 $n 表示,例如,$1 表示第一个参数,$2 表示第二个参数,依次类推。

#!/bin/bash    
    
echo "File Name: $0"
echo "First Parameter : $1"
echo "First Parameter : $2"
echo "Quoted Values: $@"
echo "Quoted Values: $*"
echo "Total Number of Parameters : $#"

运行结果为

$./test.sh Zara Ali    
    
File Name : ./test.sh
First Parameter : Zara
Second Parameter : Ali
Quoted Values: Zara Ali
Quoted Values: Zara Ali
Total Number of Parameters : 2

3. $*和$@

$* 和 $@ 都表示传递给函数或脚本的所有参数,不被双引号(" ")包含时,都以"$1" "$2" … "$n" 的形式输出所有参数。当它们被双引号(" ")包含时:

  • "$*" 会将所有的参数作为一个整体,以"$1 $2 … $n"的形式输出所有参数

  • "$@" 会将各个参数分开,以"$1" "$2" … "$n" 的形式输出所有参数。

通过下面的例子可以看到$*和$@的区别:

#!/bin/bash    
echo "\$*=" $*
echo "\"\$*\"=" "$*"
    
echo "\$@=" $@
echo "\"\$@\"=" "$@"
    
echo "print each param from \$*"
for var in $*
do
    echo "$var"
done
    
echo "print each param from \$@"
for var in $@
do
    echo "$var"
done
    
echo "print each param from \"\$*\""
for var in "$*"
do
    echo "$var"
done
    
echo "print each param from \"\$@\""
for var in "$@"
do
    echo "$var"
done

用命令./test.sh 1 2 3 4执行,会得到下面的结果

$*=  1 2 3 4   
"$*"= 1 2 3 4
$@=  1 2 3 4
$@"= 1 2 3 4
print each param from $*
1
2
3
4

print each param from $@
1
2
3
4
    
print each param from "$*"
1 2 3 4

print each param from "$@"
1
2
3
4

4.退出状态

退出状态,是指上一个命令执行后的返回结果,退出状态是一个数字,一般情况下,大部分命令执行成功会返回0,失败返回1。不过,也有一些命令为了表示不同类型的错误也会返回其他值。在Shell中可以使用 $? 获得上一个命令的退出状态,也可以获取函数的返回值。

$./test.sh 4byte fourbyte
File Name : ./test.sh
First Parameter : 4byte 
Second Parameter : fourbyte
Quoted Values: 4byte fourbyte
Quoted Values: 4byte fourbyte
Total Number of Parameters : 2
$echo $?
0
$

本文链接:http://www.4byte.cn/learning/119987/kuai-su-liao-jie-he-xue-xi-unix-linux-xia-shell-yi.html