shell 脚本语言是一种解释型语言;
shell脚本的实质是 shell命令的有序集合;
1 shell编程的基本过程
1)建立shell文件
2)赋予可执行的权限 chmod a+x filename
3)执行shell文件 ./filename
2 shell 变量
1)用户自定义变量
命名规则通C语言的命名规则;
变量赋值的方式:先写变量名,然后是 = ,紧接着是新值,中间不要有空格。当想取出变量的值时,加“$"符号即可
当赋值的内容中有空格时,注意请加上双引号; 可以使用unset +变量名 可以取消变量的赋值
例如:
定义:name=value (使用带空格的值时加引号)
访问:$name
例:FRUIT=apple
$echo $FRUIT(结果为apple)
$echo FRUIT(结果为FRUIT)
Bourne Shell只支持标量,Korn Shell中支持数组。
数组创建: name[index]=value 数组访问:${name[index]}
只读变量:readonly name
删除变量:unset name
局部变量(只在当前SHELL实例中存在),环境变量(SHELL任何子进程都能使用),SHELL变量(正确运行所必需,如PWD,PATH,HOME等)。
导出环境变量:export name
可能会见到形如 ${variable}形式的变量,变量名两侧的额外的花括号通常用来帮助识别 $ 后的变量名。
此外注意shell中的反引号的用途:
反引号 ·是在 波浪号 ~ 同一键位。 反引号允许你将shell 命令的输出赋给变量,它是脚本编程中比较重要的构件之一。
用法如: test=`date` , shell会运行反引号中的命令,并将其输出给变量test。
下面我们举例,在脚本中通过反引号获得当前日期并用他来生成唯一文件名:
- #!/bin/bash
- #date.sh
- today=`date +%y%m%d`
- ls /home > log.$today
2)位置变量
$0 : $1,......$9分别包括第一个到第九个命令行的参数 $# 命令输入的个数 $* $@ :所有命令行的参数 $? :前一个命令的退出状态 $$ 包含正在执行进程的ID号 3)环境变量 4)预定义变量3 shell程序和语句
shell程序时由 零和多条shell语句构成,shell语句包括: 说明性语句:以#开头到本行结束; 功能性语句 :shell命令、用户程序和其他shell程序; 结构性语句:条件测试,多路分支,循环语句,循环控制语句等等;1)说明性语句
在shell程序的开头加 #!/bin/sh 意思是告诉OS用那种类型的shell来解释执行改程序2)功能性语句
read 从标准输入读入一行,并赋值给后面的变量;可阻塞数学运算:
expr 算术运算命令 主要用于进行简单的整数运算:包括 加+、减-、乘 \* 、除/、 求模 %;
注意:
a)expr后的表达式个符号间需用空格隔开 b)expr支持的操作符有: |、&、<、<=、=、!=、>=、>、+、-、*、/、% c)expr支持的操作符中所在使用时需用\进行转义的有:|、&、<、<=、>=、>、* e)expr只支持整数运算注意变量与运算符之间要有空格,比如:: $ expr 1 + 9, 输出结果为 10.
此外要特别注意expr命令中的一些诡异的结果,如 expr 5 * 2, 会出错。此时我们需要使用shell的转义字符\ 来识别容易被shell错误解释
的任意字符:
- $ expr 5 \* 2
- $ 10
注意bash shell 为了保持和Bourne shell的兼容而包含了expr命令,他注意使用$ 和[ ]来实现把一个数学运算结果赋给某个变量,
如 $[ operation ] 将数学表达式圈起来:
注意:
a)$[]将中括号内的表达式作为数学运算先计算结果再输出 b)对$[]中的变量进行访问时前面需要加$ c)$[] 也只支持整数运算 、- $ v1=$[1 + 5]
- $ echo $v1
- $ 6
- $ v2=$[$v1 * 2]
- $ 12
bc是linux下的一个简单计算器,支持浮点数计算,在命令行下输入bc即进入计算器程序,而我们想在程序中直接进行浮点数计算时,利用一个简单的管道即可解决问题。
注意: 1)经我测试bc支持除位操作运算符之外的所有运算符。 2)bc中要使用scale进行精度设置 3)浮点数计算实例基本格式为: variable=`echo "options; ecpression" | bc` , 注意使用了反引号。
- var=3.14
- var=`echo "scale=2;$var*3"|bc`
- echo $var
此外可以使用 awk 进行浮点数运算:
- var=1
- var=`echo "$var 1"|awk '{printf("%g",$1*$2)}'`
- echo $var
3)浮点数计算实例
- var=3.14
- var=`echo "$var 2"|awk '{printf("%g",sin($1/$2))}'`
- echo $var
test 可测试三种对象 :字符串 整数 文件属性 tput 用于设置中断的工作模式;
3)结构性语句:
条件语句及多路分支语句用法:
1、使用if_then语句
if command then commands fi 先运行if后面的命令,如果命令的退出状态是0(成功执行命令),就将执行then后面,fi前面的所有命令。否则就跳到fi 后面继续执行。2、if-then-else语句 if command then commands else commands fi3、嵌套if语句 if command1 then commands elif command2 then commands elif command3 then commands fi4、test命令(方括号[ ]为同义词) 用于提供对条件的判断 if test condition 也可以不用test condition,而使用 [ conditon ] ([ , ] 的前后必须有空格) then commands fi condition有三种: (1)数值比较: –eq, –ne, –ge,-gt, -le, lt 但是要注意:test命令无法处理存储在变量中的浮点值。 使用bash计算器bc时,只是欺骗了shell把浮点值作为字符串值存储于一个变量中。如果只是先使用echo语句显示结果,这种方法很好。但是在面向数值的函数(如数值测试条件)中不起作用。 底线是不能在test中使用非整数变量. (2)字符串比较:=,!=,<,>,-n(检测字符串长度是否大于0),-z(检测字符串长度是否等于0) 字符串相等:测试比较将所有标点符号和大写都考虑在内 字符串顺序:要注意两点: 1)’>’,’<’一定要用’\’转义,否则shell会将它们当做重定向符号,将字符串值看做文件名 2)大于和小于的顺序与在sort命令中的顺序不同。 在test中,同一个字母,大写字母>小写字母,在sort中,相反。 字符串大小:评估一个变量是否包含数据时,使用-n和-z比较方便对空变量和未初始化的变量检测出的长度也为0 (3)文件比较 文件比较是shell脚本中最强大和最常用的一类比较。test可以测试文件状态和路径。(使用的非常频繁!) -d file: 检查file是否存在并且是一个目录 -e file: 检查file是否存在 -f file: 检查file是否存在并且是一个文件 -r file: 检查file是否存在并且可读 -s file: 检查file是否存在并且不为空 -w file: 检查file是否存在并且可写 -x file: 检查file是否存在并且可执行 -O file: 检查file是否存在并且被当前用户拥有 -G file:检查file是否存在并且默认组是否为当前用户组 file1 –nt file2: 检查file1是否比file2新 file1 –ot file2: 检查file1是否比file2旧5、复合条件查询 [ condition1 ] && [ condition2 ] [ condition1 ] || [ condition2 ]6、if-then的高级特征 (1)使用双圆括号表示数学表达式 (( expression )) expression包括除了标准数学操作符外的其他操作符如下: ++, --, !, ~, **, <<, >>, &, |, &&, || (2)使用双方括号表示高级字符串处理函数 [[ expression ]] 提供了除test命令中的标志字符串比较以外的模式匹配功能 在模式匹配中,可以定义与字符串值相匹配的正则表达式
来个if嵌套语句的例子,判断润年:
- #!/bin/bash
- # This script will test if we're in a leap year or not.
- year=`date +%Y` #命令的结果可以直接赋值给一个变量,如果要检查一个命令的返回状态,就用$?
- if [ $[$year % 400] -eq 0 ]; then
- echo "This is a leap year. February has 29 days."
- elif [ $[$year % 4] -eq 0 ]; then
- if [ $[$year % 100] -ne 0 ]; then #嵌套从这里开始
- echo "This is a leap year, February has 29 days."
- else
- echo "This is not a leap year. February has 28 days."
- fi
- else
- echo "This is not a leap year. February has 28 days."
- fi
7、case 命令 可以使用case命令,而不是编写所有的elif语句来继续检查相同的变量值。 case命令以列表导向检查单个变量的多个值: case variable in pattern1 | pattern2) commands1;; pattern3) commands2;; *) default commands;; esac
4)高级结构性语句:循环语句(for,while,until用法)
1,for循环使用方法(for/do/done)
A, for … in 语句
for 变量 in seq字符串
do action done 说明:seq字符串 只要用空格字符分割,每次for…in 读取时候,就会按顺序将读到值,给前面的变量。- #!/bin/bash
- for i in $(seq 10)
- do
- echo $i
- done
- #!/bin/bash
- for((i=1;i<=10;i++))
- do
- echo $i
- done
while 条件语句
do action done- #!/bin/bash
- i=12
- while [[ $i -gt 6 ]]
- do
- echo $i
- ((i--))
- done
until 条件
do action done 意思是:直到满足条件,就退出。否则执行action.- #!/bin/bash
- i=5
- until [[ $i -lt 0 ]]
- do
- echo $i
- ((i—))
- done
echo -n 表示 省略语句后面的换行符,默认是有换行符的;下面代码中,在打印的语句后面有 \c 表示不换行;\b表示退格 \f表示清屏;
在脚本里,用 set -x 命令将执行跟踪的功能打开,然后再用 set +x 命令关闭它。这个功能对复杂的脚本比较有用,不过这里只用简单的程序来说明:
cat > trace1.sh #! /bin/sh set -x #打开跟踪功能 echo 1st echo #做些事 set +x #关闭跟踪功能 echo 2nd echo #再做些事 ^D #以end-of-file结尾
chmod +x trace1.sh./trace1.sh + echo 1st echo #被跟踪的第一行 1st echo #命令的输出 + sex +x #被跟踪的下一行 2nd echo #下一个命令的输出
执行时,set -x 不会被跟踪,因为跟踪功能是在这条命令执行后才打开的。同理,sex +x 会被跟踪,因为跟踪功能是在这条命令执行后才关闭的。最后的echo命令不会被跟踪,因为此时跟踪功能已经关闭。
所谓的位置参数指的也就是Shell脚本的命令行参数。在Shell函数里,它们同事也可以是函数的参数。各参数都有整数来命名。基于历史原因,当它超过9,就应该用大括号把数字框起来:
echo first arg is $1 echo tenth arg is ${10}
此外,通过特殊变量,我们还可以取得参数的总数,以及一次取得所有参数。
案例
假设你想知道某个用户正使用的终端是什么,你当然可以直接使用who命令,然后在输出中自己慢慢找。这么做很麻烦又容易出错——特别是当系统的用户很多的时候。你想做的只不过是在who的输出中找到那位用户,这个时候你可以用grep命令来进行查找操作,它会列出与grep第一个参数匹配的每一行。假设你要找的用户是 betsy:
who | grep betsy betsy pts/3 Dec 27 11:07 (flags-r-us.example.com)
知道如何寻找特定的用户后,我们可以将命令放进脚本里,这段脚本的第一个参数就是我们要找的用户名称:
cat > finduser #建立新文件 #! /bin/sh # finduser --- 查看第一个参数所指定的用户是否登录 who | grep $1 ^D #以 End-of-file 结尾
chmod +x finduser #设置执行权限 ./finduser betsy #测试:寻找 betsy betsy pts/3 Dec 27 11:07 (flags-r-us.example.com)./finduser benjamin #再找找好友 Ben benjamin dtlocal Dec 27 17:55 (kites.example.com)mv finduser $HOME/bin #将这个文件存进自己的bin目录
当然,这个程序还没有达到完美。要是我们没给任何参数,会发生什么事?
finduserUsage: grep [OPTION]... PATTERN [FILE]...Try 'grep --help' for more information.
1 变量
Shell 变量名称的开头是一个字母或下划线符号,后面可以接着任意长度的字母、数字或下划线符号。
Shell 变量名称的字符长度并无限制。
Shell 变量可以用来保存字符串值,所能保存的字符数同样没有限制。
变量的赋值方式为:先写变量名称,紧接着 = 字符,最后是新值,中间完全没有任何空格。当你想取出 Shell 变量的值时,需于变量名称前面加 $ 字符。当所赋予的值内含空格时,请加上引号:
first=isaac middle=bashevis last=singer #单行可进行多次赋值 fullname="isaac bashevis singer" #值中包含空格时使用引号 oldname=$fullname #此处不需要引号
不过,当你将几个变量连接起来时,就需要使用引号了:
fullname="$first $middle $last" #这里需要双引号
2 简单的 echo 输出
$ echo Now is the time for all good men Now is the time for all good men
3 华丽的 printf 输出
printf 命令模仿 C 程序库里的 printf() 程序。它几乎复制了该函数所有的功能,如果你曾使用C、C++、awk、Perl、Python 或 Tcl 写过程序,对它的基本概念应该不陌生。当然,它在 Shell 层级的版本上,会有些差异。
如同 echo 命令, printf 命令可以输出简单的字符串:
printf "hello, world\n"
printf 不像 echo 那样会自动提供一个换行符号。你必须显示地将换行符号指定成 \n 。printf 命令的完整语法分为两部分:
printf format-string [arguments ...]
第一部分(format-string)是一个字符串,用来描述输出的排列方式,最好为此字符串加上引号。此字符串包含了按字面显示的字符以及格式声明,后者是特殊的占位符,用来描述如何显示相应的参数。格式声明分成两部分:百分比符号(%)和指示符。最常用的格式指示符有两个,%s 用于字符串,而 %d 用于十进制整数。
第二部分(arguments ...)是与格式声明想对应的参数列表。
格式字符串中,一般字符会按字面显示,转义序列则解释后再输出成相应的字符。格式什么以 % 符号开头,并以定义的字母集中的一个字符来结束,用来控制相应参数的输出。例如,%s 用户字符串的输出:
$ printf "The first program always prints '%s, %s!'\n" Hello worldThe first program always prints 'Hello world!'
4 基本的 I/O 重定向
4.1 重定向与管道
Shell 提供了数种语法标记,可以用来改变默认 I/O 的来源端与目的端。
* 以 < 改变标准输入
program < file 可将 program 的标准输入修改为 file:
tr -d '\r' < dos-file.txt
* 以 > 改变标准输出
program > file 可将 program 的标准输出修改为 file:
tr -d '\r' < dos-file.txt > UNIX-file.txt
这条命令会先以 tr 将 dos-file.txt 里的 ASCII 回车删除,再将转换完成的数据输出到 UNIX-file.txt。(dos-file.txt 里的原始数据不会有变化)。
> 重定向符在目的文件不存在时,会新建一个。然而,如果目的文件已存在,它就会被覆盖掉;原本的数据都会丢失。
* 以 >> 附加到文件
program >> file 可将 program 的标准输出附加到 file 的结尾处。
如同 >,如果目的文件不存在,>>重定向符便会新建一个。然而,如果目的文件存在,它不会直接覆盖掉文件,而是将程序所产生的数据附加到文件结尾处:
for f in dos-file*.txt do tr -d '\r' < $f >> big-UNIX-file.txtdone
* 以 | 建立管道
program1 | program2 可将 program1 的标准输出修改为 program2 的标准输入。
tr -d '\r' < dos-file.txt | sort > UNIX-file.txt
tr 命令详解
语法
tr [ options ] source-char-list replace-char-list
用途
转换字符。例如,将大写字符转换成小写。选项可让你指定要删除的字符,以及将一串重复出现的字符浓缩成一个。
常用选项
- c
取 source-char-list 的反义。tr 要转换的字符,变成未列在 source-char-list 中的字符。此选项通常与 -d 或 -s 配合使用。
- C
与 -c 相似,但所处理的是字符(可能是包含多个字节的宽字符),而非二进制的字节值。
-d
自标准输入删除 source-char-list 里所列的字符,而不是转换它们。
-s
浓缩重复的字符。如果标准输入中连续重复出现 source-char-list 里的所列的字符,则将其浓缩成一个。
行为模式
如同过滤器:自标准输入读取字符,再将结果写到标准输出。任何输入字符只要出现在 source-char-list 中,就会置换成 replace-char-list 里相应的字符。POSIX 风格的字符与等效的字符集也适用,而且 tr 还支持 replace-char-list 中重复字符的标记法。
警告
根据 POSIX 标准的定义,-c 处理的是二进制字节值,而 -C 处理的是现行 locale 所定义的字符。
4.2 特殊文件:/dev/null 与 /dev/tty
UNIX 系统提供了两个对 Shell 编程特别有用的特殊文件。第一个文件 /dev/null,就是大家所熟知的位桶(bit bucket)。传说到此文件的数据都会被系统丢掉。也就是说,当程序将数据写到此文件时,会认为它已经成功完成写入数据的操作,但实际上什么事都没做。如果你需要的是命令的退出状态,而非它的输出,次功能会很有用。例如,测试一个文件是否包含某个模式:
if grep patten myfile > /dev/nullthen ... #找到模式时 else ... #找不到模式时 fi
相对地,读取 /dev/null 则会立即返回文件结束符号。读取 /dev/null 的操作很少会出现在 Shell 程序里,不过了解这个文件的行为模式还是非常重要的。
另一个特殊文件为 /dev/tty 。当程序打开此文件时,UNIX 会自动将它重定向到一个终端 [ 一个实体的控制台或串行端口,也可能是一个通过网络与窗口登录的伪终端 ] 再也程序结合。这在程序必须读取人工输入时(例如密码)特别有用。此外,用它来产生错误信息也很方便,只是比较少人这么做:
printf "Enter new password: " #提示输入 stty -echo #关闭自动打印输入字符的功能 read pass < /dev/tty #读取密码 printf "Enter again: " #提示再输入一次 read pass2 < /dev/tty #再读取一次以确认 stty echo #别忘了打开自动打印输入字符的功能 #...
stty 命令用来控制终端的各种设置。 -echo 选项用来关闭自动打印每个输入字符的功能;stty echo 用来恢复该功能。