shell-脚本入门

http://www.blogjava.net/liubowu/archive/2007/06/25/99317.html

1 Shell环境设置

1.1 登陆欢迎信息

终端机接口 (tty1 ~ tty6) 登陆的时候,会有几行提示的字符串,在 /etc/issue(附加信息可放置在 /etc/motd)中定义。
issue 内的各代码意义:
\d 本地端时间的日期
\l 显示第几个终端机接口
\m 显示硬件的等级 (i386/i486/i586/i686...)
\n 显示主机的网络名称
\o 显示 domain name
\r 操作系统的版本 (相当于 uname -r)
\t 显示本地端时间的时间
\s 操作系统的名称
\v 操作系统的版本

1.2 bash环境配置文件

实线的方向是主要流程,虚线表示被调用的配置文件。在login shell的环境下,最终被读取的配置文件是“~./bashrc”。所以,我们可以将个人需要的设置写入该文件即可。
由于/etc/profile与~/.bash_profile都是在取得login shell的时候才会读取配置文件,所以,如果你将自己的特定设置写入上述文件后,通常需要退出后再登陆配置文件才能生效。其他我们可以留用source命令或小数点(.)将配置文件的内容读入当前shell环境中。
#source ~/.bashrc
#.  ~/.bashrc
shell-脚本入门_第1张图片

1.3 常见的环境变量

1.3.1 环境变量

存储在文件/etc/profile中

$PATH:决定了shell将到哪些目录中寻找命令或程序。
$HOME:当前用户主目录。
$MAIL:是指当前用户的邮件存放目录。
$SHELL:是指当前用户用的是哪种Shell。
$HISTSIZE:是指保存历史命令记录的条数。
$LOGNAME:是指当前用户的登录名。
$HOSTNAME:是指主机的名称,许多应用程序如果要用到主机名的话,通常是从这个环境变量中来取得的。
$LANG/LANGUGE:是和语言相关的环境变量,使用多种语言的用户可以修改此环境变量。
$PS1:是基本提示符,对于root用户是#,对于普通用户是$,也可以使用一些更复杂的值。
$PS2:是附属提示符,默认是“>”。
$IFS:输入域分隔符。当shell读取输入时,用来分隔单词的一组字符,它们通常是空格、制表符和换行符。
$0:shell脚本的名字。
$#:传递给脚本的参数个数。
$$:shell脚本的进程号,脚本程序通常会用它生成一个唯一的临时文件,如/tmp/tmfile_$


1.3.2 设置方法

echo:显示指定环境变量。
export:设置新的环境变量。
env:显示所有环境变量。
set:显示所有本地定义的shell变量。
unset:清除环境变量。

将一个路径加入到PATH变量中:

如在PATH 这个变量当中“累加”一个新目录 这个目录

#1,控制台中:
$ PATH="$PATH":/my_new_path"
#2,修改profile文件:
$ vi /etc/profile
在里面加入:
export PATH="$PATH:/my_new_path"
#3,修改.bashrc文件:
$ vi /root/.bashrc
在里面加入:
export PATH="$PATH:/my_new_path"
#后两种方法一般需要重新注销系统才能生效,最后可以通过echo命令测试一

1.4 bash中的通配符和特殊符号和组合按键

注意,这里的通配符虽然和正则表达式相似,但是是基于bash解释器解析的,而正则表达式由正则引擎的软件(如awk,grep,sed等)解析,二者完全不同。

1.4.1 通配符

* 代表0个或多个任意字符
? 代表一定有一个任意字符
[ ] [abcd],表示一个字符,或a或b或c或d
[-] [0-9],表示一个数字,0到9之间的某个
[^] [^abc],表示一个字符,且不是a、b、c

范例:
[root@linux ~]# ls test*     #那个 * 代表后面不论接几个字符都予以接受
[root@linux ~]# ls test?     #那个 ? 代表后面"一定"要接"一个"字符
[root@linux ~]# ls test???   #那个 ??? 代表"一定要接三个"字符!
[root@linux ~]# cp test[1-5] /tmp  # 将 test1, test2, test3, test4, test5 若存在的话,就拷贝到 /tmp
[root@linux ~]# cp test[!1-5] /tmp # 只要不是 test1, test2, test3, test4, test5 之外的其它 test?拷贝到 /tmp
[root@linux ~]# cd /lib/modules/`uname -r`/kernel/drivers  # 系统先执行 uname -r 找出输出结果;将结果累加在目录上面,来执行 cd 的功能!
                     = cd /lib/modules/$(uname -r)/kernel  #另外,这个 quot (`) 的功能,也可以利用 $() 来取代喔!
[root@linux ~]# cp *[A-Z]* /tmp   #表示文件中包含大写字母
[root@linux ~]# ls -lda /etc/*[35]* #表示文件中包含数字3或者5.


1.4.2 特殊字符

# 注释
\ 转义字符
| 管道(pipe)
; 连续命令
~ 用户主文件夹
$ 变量前导符
& 作业控制的后台运行
! 逻辑非
/ 目录分隔符
>,>> 数据流重定向,输出导向,分别是“替换“和”增加“
<,<< 数据流重定向,输入导向
' ‘ 单引号,不具有变量置换功能
” “ 双引号,具有变量置换功能
` ` ` `中内容可以先执行的命令,也可以用$( )来替换
( ) 在中间为子shell的起始与结束
{ } 在中间为命令块的组合

1.4.3 组合按键

Ctrl + C 终止目前的命令
Ctrl + D 输入结束(EOF),例如邮件结束的时候;
Ctrl + M 就是 Enter 啦!
Ctrl + S 暂停屏幕的输出
Ctrl + Q 恢复屏幕的输出
Ctrl + U 在提示字符下,将整列命令删除
Ctrl + Z 暂停目前的命令

1.4 数据流重定向

linux的文件描述符FD,通常为10个:FD0~FD9。可以理解为linux跟踪打开文件,分配的一个数字,linux启动后会默认打开三个文件描述符:
0 标准输入 standard input
1 标准输出standard output
2 错误输出error output 

示例1:
ls -al >   newtxt  #本来ls -al 命令预设输出到屏幕,现在被重新导出到newtxt文档。
ls -al >>  newtxt  #现将ls -al 命令输出结果“增加”到文件newtxt末尾。

示例2:想要将正确的与错误的数据分别存入不同的档案中:
• 1> :是将正确的数据输出到指定的地方去
• 2> :是将错误的数据输出到指定的地方去
$ find /home -name testing > list_right 2> list_error
有 Permission 的那几行错误信息都会跑到 list_error 这个档案中,至于正确的输出数据则会存到 list_right 这个档案中。

示例3:
将错误信息不要(置于垃圾箱中/dev/null)
$ find /home -name testing > list_right 2> /dev/null 
将正确输出和错误输出都放置到同一个文件中testing中去
$ find /home -name testing > list 2> list <==错误写法
$ find /home -name testing > list 2>&1 <==正确写法
另外,这里区分一下command>file  2>file 与  command>file 2>&1:
command > file 2>file 的意思是stdout和stderr都直接送到file中, file会被打开两次,这样stdout和stderr会互相覆盖,这样写相当使用了FD1和FD2两个同时去抢占file 的管道.
command >file 2>&1 这条命令就将stdout直接送向file, stderr 继承了FD1管道后,再被送往file,此时,file 只被打开了一次,也只使用了一个管道FD1,它包括了stdout和stderr的内容.

1.5 命令执行的判断依据

分号; cmd;cmd
多条命令顺序执行,执行顺序是从左到右的顺序。
与&& cmd1 && cmd2
若cmd1执行完毕且正确执行($?=0),则开始执行cmd2
若cmd2执行完毕且为错误($? !=0),则cmd2不执行
或|| cmd1 || cmd2
若cmd1执行完毕且正确执行($?=0),则cmd2不执行
若cmd2执行完毕且为错误($? !=0),则开始执行cmd2
注意:这里的&&和||没有优先级关系,而是按照命令次序按序执行,所以对于:
ls /tmp/abc || mkdir /tmp/abc && touch /tmp/abc/hehe
该范例总会创建/tmp/abc/hehe文件。而不是先执行后面的。
[ -f file1 ] && exit 0 || exit 1
该范例如果测试成功,则执行第一条命令,否则执行第二条命令。

1.5 shell脚本调试

Shell提供了一些用于调试脚本的选项,如下所示:
-n   读一遍脚本中的命令但不执行,用于检查脚本中的语法错误
-v   一边执行脚本,一边将执行过的脚本命令打印到标准错误输出
-x   提供跟踪执行信息,将执行的每一条命令和结果依次打印出来
$ sh -x ./script.sh

2 语法基本介绍

2.1 开头

程序必须以下面的行开始( 必须放在文件的第一行): 

#!/bin/sh 
符号#!用来告诉系统它后面的参数是用来执行该文件的程序,在这个例子中我们使用/bin/sh来执行程序。 
chmod +x filename
当编辑好脚本时,如果要执行该脚本,还必须使其可执行。 这样才能用./filename 来运行。

2.2 注释 

在进行shell编程时,以#开头的句子表示注释,直到这一行的结束。

文档可以这样写:

###################################################################################
## The script is used for the regression of lineage jar with latest hive-exec..jar
## , expression_analyze.jar, parse_common.jar, field_lineage.jar, ast_parser.jar
## in trunk.
###################################################################################
区块可以这样写
###############################################
## PARAMETERS
##

2.3 变量 

2.3.1 变量类型默认为字符串类型

#!/bin/sh 
name="Jack"
s="$name, hello!" #注意等号"="前后都不能有空格。建议字符串用双引号(单引号表示纯字符串,不支持$等)。
echo $s           #输出Jack, hello!(若s用单引号,则输出$name, hello!)
unset s           #删除变量s

2.3.2 单引号,双引号,无引号

s="./*"
echo $s     #这是变量,命令即为echo ./* ,通配符解开为:./1.sh ./a ./b ./c
echo '$s'   #这里的单引号表示一个字符串,字符串中的$等符号不能转义,输出:$s
echo "$s"   #这里的双引号表示一个字符串,字符串中的$等符号要转义,其中$s为变量,所以输出./*

2.3.3 参数扩展——花括号

num=2 
echo "this is the $numnd" #this is the 
这并不会打印出"this is the 2nd",而仅仅打印"this is the ",因为shell会去搜索变量numnd的值,但是这个变量时没有值的。可以 使用花括号来告诉shell我们要打印的是num变量: 
num=2 
echo "this is the ${num}nd" #this is the 2nd

这将打印: this is the 2nd 

2.3.4 参数扩展——正则表达式

${name:-default} 使用一个默认值(一般是空值)来代替那些空的或者没有赋值的变量name; 
${name:=default}使用指定值来代替空的或者没有赋值的变量name;
${name:?message}如果变量为空或者未赋值,那么就会显示出错误信息并中止脚本的执行同时返回退出码1。
${#name} 给出name的长度
${name%word} 从name的尾部开始删除与word匹配的最小部分,然后返回剩余部分
${name%%word} 从name的尾部开始删除与word匹配的最长部分,然后返回剩余部分
${name#word} 从name的头部开始删除与word匹配的最小部分,然后返回剩余部分
${name##word} 从name的头部开始删除与word匹配的最长部分,然后返回剩余部分
举例如下,使用cjpeg命令将一个GIF的文件转换为JPEG的文件:

$ cjpeg image.gif > image.jpg
也许有时我们会在大量的文件上进行这样的操作.这时我们如何自动重定向?我们可以很容易的这样来做:
#!/bin/sh
for image in *.gif
do
   cjpeg $image > ${image%%gif}jpg
done

2.3.4 注意变量与变量字符串的问题:

read a
if [ $a = "yes" ];then
echo "OK"
fi
if [ "$a" = "yes" ];then
echo "OK"
fi
这里$a是变量a的值,"$a"是对变量a做字符串处理结果。如果read a 时直接回车,那么$a就是空,而不是"",这时程序变成了 if [ = "yes" ]就错了,所以一般用双引号括起来,表示使用字符串值,且转义其中的$变量。

2.3.5 注意打印"*"的问题:

#!/bin/sh
sql='select * from gexing;'
echo  $sql  #其中的*会被替换成所有文件,这就相当于echo select * from table;
echo "$sql" #这就相当于echo "select * from table"
2.3.6 变量/字符串连接:
names=$var_name1$var_name2   #注意,两个变量之间没有空格
sentence=${names}"hello"     #直接连接

2.4 declare

shell中的变量类型默认为字符串类型,但可以通过declare语法来指定变量的其他类型。
语法:
declare [+/-][afrix]  variable
说明:
+/- "-"可用来指定变量的属性,"+"则是取消变量所设的属性。
-a 定义变量variable为数组(array)类型
-i  定义变量variable为整数(integer)类型
-r  定义变量variable为只读(readonly)类型
-x  用法与export一样,将variable设置为环境变量

2.5 数值计算

方法1,declare -i

示例1:让变量 sum 保存 100+300+50 的加和结果
[root@linux ~]# sum=100+300+50
[root@linux ~]# echo $sum       #结果为:100+300+50   
[root@linux ~]# declare -i sum=100+300+50
[root@linux ~]# echo $sum       #结果为:450        

示例2:生成随机数
[root@linux ~]# declare -i number=$RANDOM*10/32767 ; echo $number

方法2,var=$ (( expression ))

foo=1
foo=$(($foo+1))
echo $ (( 13 % 3 ))

2.6 数组

定义:shell支持一维数组,但并不限定数组大小,数组下标从0开始。
修改:对于数组修改操作,可以再对其重新赋值;
删除:如果要删除一个已经赋值后的元素则需要借助一个外部命令:unset,如:unset array[0]可清空下标为0的元素,此时数组大小减一;unset array[@]可以清空整个数组元素所有元素。
#!/bin/bash

#赋值
a[0]=1              #第一种赋值方法
a[1]=2
a[2]=3
b=(6,7,8,9,10)      #第二种赋值方法

#取值
echo  ${b[0]}       #用${数组名[下标]},下标是从0开始。下标是*或者@,则得到整个数组内容。输出6
echo  ${b[*]:1:3}   #直接通过${数组名[@或*]:起始位置:长度}切片原先数组,输出7 8 9
echo  ${b[*]::3}    #输出6 7 8(前三个元素)

#遍历
echo  ${b[*]}       #遍历数组除用循环外还可用:“数组名[*]”或“数组名[@]”,输出6 7 8 9 10
echo  ${b[@]}       #输出:6 7 8 9 10
for var in ${arr[@]};do echo $var; done    #分行输出:6 7 8 9 10

#数组个数
echo  ${#b[*]}      #输出:5
echo  ${#b[@]}      #输出:5

#修改和删除
b[0]=0              #修改:重新赋值
echo ${b[*]}        #输出:0 7 8 9 10
unset  b[0]          #删除:删除第一个元素
echo ${b[*]}        #输出:7 8 9 10

2.7 语句块

可以使用{}构造一个语句块。
{...}

2.7 特殊变量

举例说:
脚本名称叫test.sh 入参三个: 1 2 3
运行test.sh 1 2 3后
$* 为"1 2 3"(在一个变量中列出所有参数,各个参数之间用环境变量IFS中的第一个字符分隔开,所以如果IFS置空,则会变成"123")
$@ 为"1 2 3"($*的变体,不适用环境变量IFS,而是以空格连接参数,所以当IFS置空,则扔为"1 2 3")
$# 为3(参数数量)
$0 $n,用来存放函数调用或脚本执行时传入的参数,$0为test.sh
$1 为1,以此类推
$$ 脚本运行的当前进程的ID号
$! 后台运行的最后一个进程的ID号
$- 显示shell使用的当前选项
$? 显示最后命令的退出状态,0表示无错误(这个变量也常常用来打印输出,在脚本调试时标记某个shell命令或某个函数是否正确执行,但是要注意,$?记载的是最近的函数或命令的退出状态,因此打印时应该立即打印以获得正确的信息)

2.8 输入与输出

输入命令read
[root@linux ~]# read [-pt] variable
参数:
-p :后面可以接提示字符!
-t :后面可以接等待的『秒数!』这个比较有趣~不会一直等待使用者啦!

范例:提示使用者 30 秒内输入自己的大名,将该输入字符串做成 name 变量
[root@linux ~]# read -p "Please keyin your name: " -t 30 name
Please keyin your name: VBird Tsai
[root@linux ~]# echo $name
VBird Tsai
输出命令echo(默认有换行符)
echo函数定义了一组转义字符,在使用转义字符时要加入”-e”选项。其转义字符如下:
“/a” :响铃报警,”/b” :后退一字符,”/f” :换页,”/n” :显示换行,”/t” :制表符,”/v” :垂直制表符,”/r” :回车符,”//” :反斜线。
echo -e hello,"/n"world! 

设置颜色

设置彩色文本 重置=0 黑色=30 红色=31 绿色=32...
echo -e "\e[1;31m This is red text \e[0m"
设置彩色背景 重置=0 黑色=40 红色=41 绿色=42...
echo -e "\e[1;42m This is green background \e[0m"

注意:如果alias ls = 'ls --color' 这时候 ls | xargs tar -czvf 1.tar.gz会出现以上类似的错误tar: \033[00mxy_psp_proto.h\033[00m: Cannot stat: No such file or directory


用echo -n 去掉末尾的换行符。

echo -n "string to output"

输出命令printf(默认无换行符)

printf "hello"

2.9 Here Document:说明文字

对每个脚本写一段帮助性的文字是很有用的,此时如果我们有那个here documents,就不必用echo函数一行行输出。一个 "Here document" 以 << 开头,后面接上一个字符串,这个字符串还必须出现在here document的末尾。下面是一个例子,在该例子中,我们对多个文件进行重命名,并且使用here documents打印帮助:

#!/bin/sh
# we have less than 3 arguments. Print the help text:
if [ $# -lt 3 ] ; then
    cat << EOF
[批量换名:对文件名进行批量替换]
USAGE:      ren 'regexp' 'replacement' files...
EXAMPLE:    rename all *.HTM files in *.html:
    sh $0 'HTM$' 'html' *.HTM
HELP
EOF
    exit 0
fi

OLD="$1"
NEW="$2"
# The shift command removes one argument from the list of
# command line arguments.
shift
shift
# $* contains now all the files:
for file in $*; do
    if [ -f "$file" ] ; then
        #这里的|是管道,将源文件名进行词汇替换
        newfile=`echo "$file" | sed "s/${OLD}/${NEW}/g"`
        if [ -f "$newfile" ]; then
            echo "ERROR: $newfile exists already"
        else
            echo "renaming $file to $newfile ."
            mv "$file" "$newfile"
        fi
    fi
done
分析:

这是一个复杂一些的例子。让我们详细讨论一下。第一个if表达式判断输入命令行参数是否小于3个 (特殊变量$# 表示包含参数的个数) 。如果输入参数小于3个,则将帮助文字传递给cat命令,然后由cat命令将其打印在屏幕上。打印帮助文字后程序退出。 如果输入参数等于或大于3个,我们就将第一个参数赋值给变量OLD,第二个参数赋值给变量NEW。下一步,我们使用shift命令将第一个和第二个参数从 参数列表中删除,这样原来的第三个参数就成为参数列表$*的第一个参数。然后我们开始循环,命令行参数列表被一个接一个地被赋值给变量$file。接着我 们判断该文件是否存在,如果存在则通过sed命令搜索和替换来产生新的文件名。然后将反短斜线内命令结果赋值给newfile。这样我们就达到了我们的目 的:得到了旧文件名和新文件名。然后使用mv命令进行重命名。  

2.10 退出状态

在Linux系统中,每当命令执行完成后,系统都会返回一个退出状态。该退出状态用一整数值表示,用于判断命令运行正确与否。
若退出状态值为0,表示命令运行成功
若退出状态值不为0时,则表示命令运行失败
最后一次执行的命令的退出状态值被保存在内置变量“$?”中,所以可以通过echo语句进行测试命令是否运行成功

2.11 测试结构与操作符

测试结构
测试命令可用于测试表达式的条件的真假。如果测试的条件为真,则返回一个 0值;如果测试的条件为假,将返回一个非 0整数值。
测试命令有两种结构:
一种命令是使用test命令进行测试,格式为: 
test expression
其中条件expression是一个表达式,该表达式可由数字、字符串、文本和文件属性的比较,同时可加入各种算术、字符串、文本等运算符。
另一种命令格式:
[ expression ]
其中“[ ”是启动测试的命令,但要求在expression后要有一个“ ]”与其配对。使用该命令时要特别注意“[”后和“]”前的空格是必不可少的。
可以使用 man test来查询所有操作符。

整数比较运算符
num1 -eq num2 如果 num1等于num2,测试结果为0
num1 -ge num2 如果 num1大于或等于num2,测试结果为0
num1 -gt num2 如果 num1大于num2,测试结果为0
num1 -le num2 如果 num1小于或等于num2,测试结果为0
num1 -lt num2 如果 num1小于num2,测试结果为0
num1 -ne num2 如果 num1不等于num2,测试结果为0

字符串运算符
字符串运算符用于测试字符串是否为空、两个字符串是否相等或者是否不相等
string         测试字符串string是否不为空
-n string 测试字符串string是否不为空
-z string 测试字符串string是否为空
string1 = string2 测试字符串string1是否与字符串string2相等
string1 != string2 测试字符串string1是否与字符串string2 不相等

文件操作符
Linux Shell提供了大量的文件操作符,这样可以完成测试文件的各种操作。比较常用的文件操作符如下表所示:
-d file          测试file是否为目录
-e file         测试file是否存在
-f file         测试file是否为普通文
-r file         测试file是否是可读文件
-s file         测试file的长度是否不为0
-w file         测试file是否是可写文件
-x file         测试file是否是可执行文件
-L file         测试file是否符号化链接

逻辑运算符:
逻辑运算符主要包括逻辑非、逻辑与、逻辑或运算符:
!expression     如果expression为假,则测试结果为真
expression1 –a expression2 如果expression1和expression同时为真,则测试结果为真
expression1 –o expression2     如果expression1和expression2中有一个为真,则测试条件为真

----------------------------------------------------------------------------------------------------------------------------------------
算术运算符
在Linux Shell中,算术运算符包括:+(加运算)、-(减运算)、*(乘运算)、/(除运算)、%(取余运算)、**(幂运算),这些算术运算符的举例及其结果如下表所示:
运算符            举例      结果
+(加运算)    3+5       8
-(减运算)    5-3       2
*(乘运算)    5*3       15
/(除运算)    8/3        2
%(取余运算)    15%4        3
**(幂运算)    5**3       125

位运算符
位运算符在Shell编程中很少使用,通常用于整数间的运算,位运算符是针对整数在内存中存储的二进制数据流中的位进行的操作。
<<(左移)         value=4<<2  4左移2位,value值为16
>>(右移)         value=8>>2   8右移2位,value值为2
&(按位与)         value=8&4   8按位与4,value值为0
|(按位或)         value=8|4   8按位或4,value值为12
~(按位非)          value=~8     按位非8,value值为-9
^(按位异或)          value=10^3    10按位异或3,value值为9

自增自减运算符
自增自减操作符主要包括前置自增(++variable) 、前置自减(--variable)、后置自增(variable++)和后置自减(variable--)。
前置操作首先改变变量的值(++用于给变量加1,--用于给变量减1),然后在将改变的变量值交给表达式使用
后置变量则是在表达式使用后再改变变量的值
要特别注意自增自减操作符的操作元只能是变量,不能是常数或表达式,且该变量值必须为整数型,例如:++1、(num+2)++都是不合法的

3 shell命令与流程控制

在shell脚本中可以使用三类命令: 

3.1 Unix 命令: 

shell脚本中如何使用unix命令:
1,直接使用(一行一个)
ls | grep "core"
2,使用反斜线
tar -zcvf lastmod.tar.gz `find . -mtime -1 -type f -print` 
3,使用$()
more $(grep -l POSIX *)

常用命令语法及功能

echo "some text" 将文字内容打印在屏幕上 
ls 文件列表 
wc –l file
wc -w file
wc -c file
计算文件行数
计算文件中的单词数
计算文件中的字符数 
cp sourcefile destfile 文件拷贝 
mv oldname newname 重命名文件或移动文件 
rm file 删除文件 
grep 'pattern'  file 在文件内搜索字符串比如:grep 'abc' 1.txt
cut -b colnum file 指定欲显示的文件内容范围,并将它们输出到标准输出设备
比如:输出每行第5个到第9个字符cut -b5-9 file.txt
千万不要和cat命令混淆,这是两个完全不同的命令 
cat file.txt 输出文件内容到标准输出设备(屏幕)上 
file somefile 得到文件类型 
read var 提示用户输入,并将输入赋值给变量 
sort file.txt 对file.txt文件中的行进行排序 
uniq 删除文本文件中出现的行列
比如: sort file.txt | uniq 
expr 进行数学运算Example: add 2 and 3expr 2 "+" 3 
find 搜索文件
比如:根据文件名搜索find . -name filename -print 
tee 将数据输出到标准输出设备(屏幕) 和文件
比如:somecommand | tee outfile 
basename file 返回不包含路径的文件名
比如: basename /bin/tux将返回 tux
dirname file 返回文件所在路径
比如:dirname /bin/tux将返回 /bin
head file
tail file
打印文本文件开头几行
打印文本文件末尾几行
sed

Sed是一个基本的查找替换程序。
可以从标准输入(比如命令管道)读入文本,
并将结果输出到标准输出(屏幕)。
该命令采用正则表达式(见参考)进行搜索。 
不要和shell中的通配符相混淆。
比如:将linuxfocus 替换为 LinuxFocus :
cat text.file | sed 's/linuxfocus/LinuxFocus/' > newtext.file

awk

awk 用来从文本文件中提取字段。
缺省地,字段分割符是空格,可以使用-F指定其他分割符。
cat file.txt | awk -F, '{print $1 "," $3 }'
这里我们使用,作为字段分割符,同时打印第一个和第三个字段。
如果该文件内容如下: Adam Bor, 34, IndiaKerry Miller, 22, USA
命令输出结果为:Adam Bor, IndiaKerry Miller, USA 

3.3 流程控制 

3.3.1.if语句 

"if" 表达式 如果条件为真则执行then后面的部分: 
if ....; then 
  .... 
elif ....; then 
  .... 
else 
  .... 
fi
大多数情况下,可以使用测试命令来对条件进行测试。比如可以比较字符串、判断文件是否存在及是否可读等等… 

通常用" [ ] "来表示条件测试注意这里的空格很重要。要确保方括号的空格。 

[ ! -d $CASE ] && echo "No case path $CASE, quit!" && exit 1 #判断如果没有目录$CASE就退出

[ -f "somefile" ] :判断是否是一个文件 
[ -x "/bin/ls" ] :判断/bin/ls是否存在并有可执行权限 
[ -n "$var" ] :判断$var变量是否有值 
[ "$a" = "$b" ] :判断$a和$b是否相等 
执行man test可以查看所有测试表达式可以比较和判断的类型。 

直接执行以下脚本: 

#!/bin/sh
if [ "$SHELL" = "/bin/bash" ]; then
    echo "your login shell is the bash (bourne again shell)"
else
    echo "your login shell is not bash but $SHELL"
fi

变量$SHELL包含了登录shell的名称,我们和/bin/bash进行了比较。 

快捷操作符 
熟悉C语言的朋友可能会很喜欢下面的表达式: 
[ -f "/etc/shadow" ] && echo "This computer uses shadow passwors" 
这里 && 就是一个快捷操作符,如果左边的表达式为真则执行右边的语句。您也可以认为是逻辑运算中的与操作。上例中表示如果/etc/shadow文件存在则打印” This computer uses shadow passwors”。同样或操作(||)在shell编程中也是

可用的。这里有个例子:

#!/bin/sh
mailfolder=./lineage_case
[ ! -r "$mailfolder" ]&&{ echo "Can not read $mailfolder" ; exit 1; }
dir="${mailfolder}/*"
#grep "^From" `echo "$dir"` #
grep "^From" $dir  #这两者效果相同,关键在于要知道这里的*要表达的是真正的*,还是表示通配,这里表示通配

sql="select * from table;"
echo "$sql"        #这里要表达的是真正的*,而不是通配

该脚本首先判断mailfolder是否可读。如果可读则打印该文件中的"From" 一行。如果不可读则或操作生效,打印错误信息后脚本退出。{}构成匿名函数。

3.3.2.case

case :表达式可以用来 匹配一个给定的字符串,而不是数字。 
case ... in 
...) do something here ;; 
esac 
让我们看一个例子。 file命令可以辨别出一个给定文件的文件类型,比如: 
file lf.gz 
  这将返回: 
lf.gz: gzip compressed data, deflated, original filename, 
last modified: Mon Aug 27 23:09:18 2001, os: Unix 
我们利用这一点写了一个叫做smartzip的脚本,该脚本可以自动解压bzip2, gzip 和zip 类型的压缩文件: 
#!/bin/sh 
ftype=`file "$1"` 
case "$ftype" in 
"$1: Zip archive"*) 
  unzip "$1" ;; 
"$1: gzip compressed"*) 
  gunzip "$1" ;; 
"$1: bzip2 compressed"*) 
  bunzip2 "$1" ;; 
*) echo "File $1 can not be uncompressed with smartzip";; 
esac 
您可能注意到我们在这里使用了一个特殊的变量$1。该变量包含了传递给该程序的第一个参数值。也就是说,当我们运行: 
smartzip articles.zip 
$1 就是字符串 articles.zip 


再看一例

#!/bin/sh
echo “Is it morning? Please answer yes or no”
read timeofday
case “$timeofday” in
    [yY] | [Yy][Ee][Ss])
           echo “Good Morning”;;
    [nN]*)
           echo “Good Afternoon”;;
    *)
           echo “Sorry, answer not recognized”
           exit 1
           ;;
esac
exit 0

3.3.3. selsect

select 表达式是一种bash的扩展应用,尤其擅长于交互式使用。用户可以从一组不同的值中进行选择。 
select var in ... ; do 
 break 
done 
.... now $var can be used .... 
下面是一个例子: 
#!/bin/sh 
echo "What is your favourite OS?" 
select var in "Linux" "Gnu Hurd" "Free BSD" "Other"; do 
    break 
done 
echo "You have selected $var" 
下面是该脚本运行的结果: 
What is your favourite OS? 
1) Linux 
2) Gnu Hurd 
3) Free BSD 
4) Other 
#? 1 
You have selected Linux 

3.3.4.while

while表达式: 
while ...; do 
.... 
done 

while-loop 将运行直到表达式测试为真。关键字"break" 用来跳出循环。而关键字”continue”用来不执行余下的部分而直接跳到下一个循环。 

例如:

#!/bin/sh
i=1
while [ "$i" -le 10 ];do
    echo "num is $i"
    i=$((i+1))
done
或:

#!/bin/sh
i=1
while [ "$i" -le 10 ]
do
    echo "num is $i"
    i=$((i+1))
done

3.3.5 until

until 条件
do
  语句
done


以下代码直到有内容时返回值才为0,因此跳过do|done,然后输出echo。其中的>/dev/null只是为了将结果不在屏幕显示,不影响返回值。

until who | grep "$1" > /dev/null
do
 sleep(60)
done

echo "$1 has logged in"

3.3.6.for

for-loop表达式查看一个字符串列表 (字符串用空格分隔) 然后将其赋给一个变量: 
for var in ....; do 
  .... 
done 
在下面的例子中,将分别打印ABC到屏幕上: 
#!/bin/sh 
for var in A B C ; do 
  echo "var is $var" 
done 
下面是一个更为有用的脚本showrpm,其功能是打印一些RPM包的统计信息: 
#!/bin/sh 
# list a content summary of a number of RPM packages 
# USAGE: showrpm rpmfile1 rpmfile2 ... 
# EXAMPLE: showrpm /cdrom/RedHat/RPMS/*.rpm 
for rpmpackage in $*; do 
 if [ -r "$rpmpackage" ];then 
  echo "=============== $rpmpackage ==============" 
  rpm -qi -p $rpmpackage 
 else 
  echo "ERROR: cannot read file $rpmpackage" 
 fi 
done 
这里出现了第二个特殊的变量$*,该变量包含了所有输入的命令行参数值。
如果您运行showrpm openssh.rpm w3m.rpm webgrep.rpm ,此时 $* 包含了 3 个字符串,即openssh.rpm, w3m.rpm and webgrep.rpm. 


for的另一种用法:

for (( 初始值; 限制值; 执行步长 ))
do
     程序段
done

例如:

#!/bin/bash
# Program:
# 	Try do calculate 1+2+....+${your_input}
# History:
# 2005/08/29	VBird	First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH


read -p "Please input a number, I will count for 1+2+...+your_input: " nu


s=0
for (( i=1; i<=$nu; i=i+1 ))
do
	s=$(($s+$i))
done
echo "The result of '1+2+3+...+$nu' is ==> $s"

3.3.7 beak

退出循环,类似C。

break

3.3.8 continue

进入下一次循环,类似C。

continue

3.4 source命令和dot命令

通常,当一个脚本执行一条外部命令或者脚本程序时,它会创建一个新的环境,也就是一个子shell,命令将在这个新环境中执行,在执行完毕后,这个环境被丢弃,留下退出码返回给父shell。但是外部的source命令和点命令在执行脚本程序中列出的命令时,使用的是调用该脚本程序的同一个shellsource命令或dot命令将外部命令放在当前进程(而不是子进程)环境中运行(类似c语言中的 #include语句),并继承当前进程(shell)的所有变量

范例:用dot命令修改当前进程的环境变量
以下是文件classic_set (当前目录下)的内容:

version=classic
PATH=/usr/local/old_bin:/usr/bin:/bin:. 
PS1=“classic>”
以下是文件latest_set (当前目录下)的内容:
version= latest
PATH=/usr/local/new_bin:/usr/bin:/bin:.
PS1=“latest >”
用dot 命令执行这两个shell脚本:
$ . ./classic_set 
classic> echo $version 
classic 
classic> . latest_set 
latest > echo $version 
latest 
latest >

3.5 export命令

在默认情况下,在一个shell里创建的变量在此shell调用的下级(子)shell里是不可用的。

export命令将作为它参数的变量导出到子shell中,并使之在子shell中有效,一旦一个变量被shell到处,它就可以被该shell调用的任何脚本使用,也可以被后续依次调用的任何shell使用。

3.6 exec命令

将当前的shell替换为一个不同的程序,wall会替换整个脚本,然后原脚本exec语句后面的脚本内容都不会再继续运行。

...
exec wall "that's all,we will finish now"
...

3.7 shift命令

依次扫描所有的位置参数:
#!/bin/sh
while [ "$1" != "" ];do
	echo "$1"
	shift
done
exit 0

3.8 trap命令

trap命令用来指定在接收到操作系统信号之后将要采取的行动。脚本程序通常是从头到尾解释并运行的,所以必须把trap命令放在准备保护的脚本程序部分之前

trap命令常见的用途在于脚本程序中断时完成清理工作,比如临时文件等。例子程序如下:

#!/bin/sh
trap 'rm -f /tmp/my_tmp_file_$$' INT
echo creating file /tmp/my_tmp_file_$$
date > /tmp/my_tmp_file_$$
echo "press interrupt (CTRL-C) to interrupt ......"
while [ -f /tmp/my_tmp_file_$$ ]; do
       echo File exists
       sleep 3
done
echo we never get here
exit 0


在这个脚本程序里,先用trap命令安排它在出现INT(中断)信号时执行“rm -f /tmp/my_tmp_file_$$”命令删除临时文件。然后让脚本程序进入一个while循环,在临时文件存在的情况下不断循环。当用户按下CTRL-C组合键时,语句rm -f /tmp/my_tmp_file_$$ 就会被执行,然后继续下一个循环。但是因为临时文件已经被删除了,所以while循环在第一次执行时就会正常退出。注:$$表示当前的shell进程号。运行该脚本,首先在tmp下创建临时文件my_tmp_file_$$,如果遇到INT中断,即Ctrl+c,即删除该文件。


3.9 多进程控制

使用后台运行的方式 & 以及wait函数来控制多进程步骤

#!/bin/bash

sh aa.sh

sh bb.sh &
p1=$!
sh cc.sh &
p2=$!

wait $p1 && wait $p2 

这样 先做aa.sh,等aa.sh做完了再做bb和cc,等bb和cc都做完了再做后面的。


5 函数

5.1 函数定义语法 

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

说明:
1、可以带function f()  定义,也可以直接f() 定义,不带任何参数。
2、函数的参数默认保存在$1,$2,...中,$0中保存函数名。
3、函数的返回值默认是最后一条命令的运行结果,除非显示有return n(0-255)。返回值由$?获取。
4、先定义后使用。

5.2 函数返回值的两种办法

其一,函数内部使用 return 这个命令,出函数后立刻用$?访问,return只能返回非负数整数 且超过255的会反卷,也就是256就会变成0。

函数内部:return,函数外部:echo $?

#!bin/sh
function sum()  
{
    return $(($1+$2))
}

sum 1 2
echo $?

其二,函数内部使用echo这个命令输出想要返回的值,同时调用函数的时候把函数的执行结果赋值给一个变量,那么这个变量里才是真正想要的返回值。

函数内部 echo,函数外部:c = $( sum 1 2)

#!bin/sh
function sum()  
{
    echo $(($1+$2))
}

c=$(sum 1 2)
echo $c

5.3 实例分析

#!bin/sh
function sum()  
{
    echo $(($1+$2))
    return $(($1-$2))
}
sum $1 $2       #这里因为调用函数,所以输出5+2=7
c=$(sum $1 $2)  #这里调用函数被当做一条命令,因此其中的echo不起作用,因此只将函数体内最后一条语句的结果(而非返回值)保存在c中,即echo的结果。5+2=7. 
echo $?         #这是上一个函数调用的返回值,为3,(这里用$?,所以函数内部找return)
echo $c         #这里用$c,所以函数内部找echo
运行结果:

$ . 1.sh 5 2
7
3
7
c=$(sum $1 $2);  在shell中,但括号里是命令语句。 因此,我们可以将shell中函数,看作是定义一个新的命令。

范例:下面是一个叫做xtitlebar的脚本,使用这个脚本您可以改变终端窗口的名称。这里使用了一个叫做help的函数。正如您可以看到的那样,这个定义的函数被使用了两次。 

#!/bin/sh
# vim: set sw=4 ts=4 et:
help()
{
    cat <

再例:

我们已经见过$* 和 $1, $2 ... $9 等特殊变量,这些特殊变量包含了用户从命令行输入的参数。迄今为止,我们仅仅了解了一些简单的命令行语法(比如一些强制性的参数和查看帮助的-h选项)。 但是在编写更复杂的程序时,您可能会发现您需要更多的自定义的选项。通常的惯例是在所有可选的参数之前加一个减号,后面再加上参数值 (比如文件名)。
#!/bin/sh
help()
{
    cat < shift by 2
    --) shift;break;; # end of options
    -*) echo "error: no such option $1. -h for help";exit 1;;
    *) break;;
esac
done
命令: 
cmdparser -l hello -f -- -somefile1 somefile2 
结果: 
opt_f is 1
opt_l is hello
first arg is -somefile1
2nd arg is somefile2

原理:
这个脚本是如何工作的呢?脚本首先在所有输入命令行参数中进行循环,将输入参数与case表达式进行比较,如果匹配则设置一个变量并且移除该参数。根据unix系统的惯例,首先输入的应该是包含减号的参数.



6 我的脚本模板

templet.sh
###################################################################################
## The script is used for the test of ABC.jar in myApps
## @author:xing.gexing
## @date 2012-10-26 下午4:05:07
## @version V1.0
###################################################################################


###############################################
## PARAMETERS
##
SMOKE='true'
SMOKE_MAILTO='xing.gexing\@xxx.com'
MAILTO='xing.gexing\@xxx.com'
WHITE_LIST='white.list'

DIR='/home/admin'
WORKSPACE="${DIR}/myApps"
[ ! -d $WORKSPACE ] && echo "No case path $WORKSPACE, quit!" && exit 1

##############################################
## BUILD ABC.jar
##
SOURCE="$WORKSPACE/source"
DEST="$WORKSPACE/dest"
cd $TRUNK
svn up --username=xxx --password=ooo
[ $? -ne 0 ] && echo "svn up failed!!! quit!" && exit 1
ant clean && ant package
[ $? -ne 0 ] && echo "build ABC.jar failed!!! quit!" && exit 1
cp $TRUNK/build/dist/*.jar $DEST/lib


[ "$SMOKE" == 'true' ] && MAILTO=$SMOKE_MAILTO && WHITE_LIST=$WORKSPACE/white.list

sed -i "s@#MAIL_TO#@$MAILTO@" $ENV_CFG
sed -i "s@#WHITE_LIST#@$WHITE_LIST@" $ENV_CFG
sed -i "s@#TRUNK_PATH#@$WORKSPACE@" $ENV_CFG

###############################################
## run
##
python test.py -abcd

7 实例

#!/bin/sh

##############################################  
#Author GE Xing
#Time   2013 05 16

##############################################
# column title
#
echo -e "Reduce, jvm start, reduce done, reduce duration, write size, writing start, writing end, writing duration, real writing start, real writing end, real writing duration"


for line in `cat "./ids.result"`
do

	##############################################  
	## task_name(REDUCE)  
	## 
	reduce_name=$line
	#echo ----reduce_name: 
	echo -e "${reduce_name}, \c"


	##############################################
	## jvm start time
	##
	jvm_start=`cat mapAndReduce.result| grep $reduce_name | grep "given task" | awk '{print $1, $2}'`
	#time_jvm_start=`date -d "$jvm_start" +%s`
	time_jvm_start=$(transTime "$jvm_start")
	#echo ----jvm_start: 
	echo -e "${time_jvm_start}, \c"


	##############################################
	## reduce done 
	##
	reduce_done=`cat mapAndReduce.result| grep $reduce_name | grep " is in commit-pending, task state:" | awk '{print $1, $2}'`
	time_reduce_done=$(transTime "$reduce_done")
	#echo ----reduce_done: 
	echo -e "${time_reduce_done}, \c"



	##############################################
	## reduce duration
	##
	reduce_duration=`expr $time_reduce_done - $time_jvm_start`
	#echo ----reduce_duration: 
	echo -e "${reduce_duration}, \c"


	##############################################
	## write size
	##
	write_size=`cat mapAndReduce.result| grep $reduce_name | grep "op: HDFS_WRITE, cliID:" | awk 'NR==1 {print $10}'`
	#echo ----write_size: 
	echo -e "${write_size} \c"


	##############################################
	## writing start time
	##
	writing_start=`cat mapAndReduce.result| grep $reduce_name | grep "NameSystem.allocateBlock" | awk '{print $1, $2}'`
	time_writing_start=$(transTime "$writing_start")
	#echo ----writing_start: 
	echo -e "${time_writing_start}, \c"


	##############################################
	## writing end time
	##
	writing_end=`cat mapAndReduce.result| grep $reduce_name | grep "Removing lease on  file" | awk '{print $1, $2}'`
	time_writing_end=$(transTime "$writing_end") 
	#echo ----writing_end: 
	echo -e "${time_writing_end}, \c"



	##############################################
	## writing duration
	##
	writing_duration=`expr $time_writing_end - $time_writing_start`
	#echo ----writing_duration: 
	echo -e "${writing_duration}, \c"

	##############################################
	## real writing start time
	##
	T=`cat mapAndReduce.result| grep $reduce_name | grep "op: HDFS_WRITE, cliID:" | awk '{print $1, $2, $22}'`
	#echo -e "T $T\n"
	t1=`echo $T |awk '{print $1, $2}'`
	#echo -e "t1 $t1\n"
	r1=$(transTime "$t1")
	#echo -e "r1 $r1\n"
	d1=`echo $T |awk '{print $3/1000}'`
	#echo -e "d1 $d1\n"
	b1=`expr $r1 - $d1`
	#echo -e "b1 $b1\n"

	#t2=`echo $T |awk '{print $4, $5}'`
	#r2=`date -d "$t2" +%s`
	#d2=`echo $T |awk '{print $6}'`
	#b2=`expr $r2 - $d2`
	#
	#t3=`echo $T |awk '{print $7, $8}'`
	#r3=`date -d "$t3" +%s`
	#d3=`echo $T |awk '{print $9}'`
	#b3=`expr $r3 - $d3`
	#
	#min=$b1
	#if test $b2 -lt $min
	#	then 
	#		min=$b2
	#fi 
	#if test $b3 -lt $min
	#	then 
	#		min=$b3
	#fi 
	#
	#max=$b1
	#if test $b2 -gt $max
	#	then 
	#		max=$b2
	#fi 
	#if test $b3 -gt $max
	#	then 
	#		max=$b3
	#fi
	 
	real_writing_start=$b1
	#echo ----real_writing_start:
	echo -e "${real_writing_start}, \c"

	##############################################
	## real_writing end time
	##
	real_writing_end=$r1
	#echo ----real_writing_end:
	echo -e "${real_writing_end}, \c"

	##############################################
	## real_writing duration
	##
	#real_time_writing_start=`date -d "$real_writing_start" +%s`
	#real_time_writing_end=`date -d "$real_writing_end" +%s`
	#real_writing_duration=`expr $real_time_writing_end - $real_time_writing_start`
	real_writing_duration=`expr $real_writing_end - $real_writing_start`
	#echo ----real_writing_duration:
	echo -e "${real_writing_duration}"


done

function transTime
{
    echo "$1" | awk -F'[:, ]' '{print ($4+$3*60+$2*60*60)*1000+$5}'
}

 bash -v sum.sh表示将代码执行之前,先将代码显示出来

[gexing111@gexing111 myapps]$ bash -v sum.sh

#!/bin/bash
let a=30
let b=20
let sum="$a + $b"
if test $a -lt $b
 then
   echo "OK"
 else
   echo "NO"
fi

NO

 bash -v sum.sh表示将代码执行过程中显示出来

[gexing111@gexing111 myapps]$  bash -x sum.sh
+ let a=30
+ let b=20
+ let 'sum=30 + 20'
+ test 30 -lt 20
+ echo NO

NO

程序1:Hello.sh

#!/bin/bash
clear
echo "Welcome to my first Linux Program!"
echo "Enter your name :"
read response
echo "Hello,$response!"

程序2:readEmployees.sh

#!/bin/bash
clear
echo "Plz input your Name"
read Name
echo "Plz input your Age"
read Age
echo "Name:$Name Age:$Age">>mydata.dat

echo ""
echo "Employees:"
echo ""
cat mydata.dat

程序3:sum.sh

#!/bin/bash
let a=30
let b=20
let sum="$a + $b"
if test $a -lt $b
 then
   echo "OK"
 else
   echo "NO"
fi

程序4:mymenu.sh

#!/bin/bash
declare flag="1"
while [ $flag -eq "1" ]
do
        echo "The Telephone Book"
        echo ""
        echo "1.Display A Telephone Number"
        echo "2.Add A New Telephone NUmber"
        echo ""
        echo "Q Quit"
        echo ""
        echo "Enter your selection:"
        read selection

        case $selection in
        "1")
                echo "You want to display a telephone number."
                getnum
                ;;
        "2")
                echo "You want to add a new telephone number."
                addnum
                ;;
        "q")
                flag="0"
                ;;
        "Q")
                flag="0"
                ;;
        *)
                echo "You made an invalid selection."
        esac
done
5,程序friends.sh

#!/bin/bash
for friend in "Mary Jones" "Joe Smith" "Sue Jones"
do
        echo "Hello,$friend"
done
6,程序raining.sh

#!/bin/bash
declare raining="1"
while [ $raining -eq "1" ]
do
        clear
        echo ""
        echo "Is it raining?"
        echo ""
        echo "1,YES"
        echo "2,NO"
        echo ""
        echo "Enter your selection:"
        read raining
done

echo "It stopped raining."

7,程序blink.sh

#!/bin/bash
clear
declare count1=1
declare count2

while [ $count1 -lt 6 ]
do
        echo "Warning:There is a bug in your program!"
        let count2=1
        while [ $count2 -lt 20000 ]
        do
                let count2="$count2 + 1"
        done
        clear
        let count2=1
        while [ $count2 -lt 20000 ]
        do
                let count2="$count2 + 1"
        done

        let count1="$count1 + 1"
done

8,程序break.sh

#!/bin/bash
let n=1
while [ $n -eq 1 ]
do
        echo "Enter your name or \"stop\" to end:"
        read name
        case $name in
        "stop")
                echo "Bye!"
                break
                ;;
        *)
                echo "Hi,$name!"
                ;;
        esac
done

9,程序function_dis.sh

#!/bin/bash
clear
function display
{
        echo "Welcome to the world"
        echo "of functions."
}

display

10,程序function_verify.sh

#!/bin/bash
clear
function verify
{
        if [ $# -ne 2 ]
        then
                echo "Wrong number of arguments!"
        #字符串的比较用=  而数字的比较用-eq
        elif [ $1 = "gexing111" ] && [ $2 = "111gexing" ]
        then
                echo "Verified"
        else
                echo "Rejected"
        fi
}
verify $1 $2






你可能感兴趣的:(linux)