linux shell编程bash编程shell教程bash教程shell文档bash文档shell脚本bash脚本教程第一部分:绪论
Posted Dontla
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux shell编程bash编程shell教程bash教程shell文档bash文档shell脚本bash脚本教程第一部分:绪论相关的知识,希望对你有一定的参考价值。
原文:http://shouce.jb51.net/shell/
文章目录
第一部分:简介
Shell是一个命令解释器。它不仅是操作系统内核与用户之间的绝缘层,同时也是一种功能相当强大的编程语言。
一个Shell程序,通常称为脚本,它是一个由系统调用,命令工具,软件包和已编译的二进制包"粘合" 起来的极易使用的工具。
事实上,整个UNIX系统命令,软件包和工具都能由一个shell脚本调用。
如果这还不够,Shell的内部命令,比如测试和循环结构,都使Shell脚本更强大和更有弹性。
Shell脚本在系统管理任务表现非常的出色,并且对于日常反复性的处理工作避免了使用那些结构过于复杂的程序语言。
第一章:为什么要有Shell编程?
没有一种编程语言是完美的。甚至也没有一种最好的语言;只有一种非常合适或可能非常不合适实际目标的语言。—— Herbert Mayer
脚本应用知识对于希望相当精通系统管理的任何人来说是必需的,即使他实际上并不想写一个脚本程序。一般来说一个Linux机器启动后,它会执行在/etc/rc.d
目录下的Shell脚本重建系统环境并且启动各种服务。理解这些启动脚本的细节对分析系统运作行为并修改它是意义重大的。
编写shell脚本不是很难学,因为脚本内建的功能集(check?)和他们只有相当少的shell的操作符和选项 [1] 需要学。语法非常的简单易懂,就像在命令行上调用和连接软件包一样容易,它仅有一些少量的 “规则” 需要掌握。大多数短小的脚本第一次就工作的很好,即使是较长的脚本调试也相当的容易。
shell脚本是一个复杂应用原型的"quick and dirty" 方法。在项目开发中用shell编程实现一个有限的功能性子集常常是有用的开始。用这种方法去测试应用程序的结构和模块组合,可以在实际地用C,C++,Java或者Perl进行编程之前发现主要的设计缺陷。
Shell编程遵从经典UNIX哲学:把复杂的问题分解成简单的小问题,然后再把各部分功能组合起来解决复杂问题。这和用新一代高级的多用途的语言,例如Perl,试图成为所有人处理所有事情的语言但是所付出的代价是强迫改变你的思维方法来适应这种工具,大多数人认为这是一个更好的或者至少感觉上更令人能接受的方法。
什么时候不适合使用Shell编程:
-
资源紧张的项目,特别是那些速度是重要因素的地方(排序,散序,等等)
-
程序要进行很复杂的数学计算,特别是浮点计算,任意精度的计算,或者是复数计算(应该用C++或是FORTRAN代替)
-
要求交叉编译平台的可移植性(使用C或者是Java代替)
-
需要结构化编程的复杂应用(需要变量类型检查和函数原型等等)
-
对于影响系统全局性的关键任务应用。
-
安全非常重要。你必须保证系统完整性和抵抗入侵,攻击和恶意破坏。
-
项目由连串的依赖的各个部分组成。
-
多种文件操作要求(Bash被限制成文件顺序存取,并且是以相当笨拙,效率低下的逐行的存取方式)
-
需要良好的多维数组支持。
-
需要类似链表或树这样的数据结构。
-
需要产生或操作图象或图形用户界面。
-
需要直接存取系统硬件。
-
需要端口号或是socket I/O。
-
需要使用可重用的函数库或接口。
-
所有的私有的不开源的应用程序(Shell脚本的源代码是直接可读,能被所有人看到的)
如果你需要有上面的任意一种应用,请考虑其他的更强大的脚本语言――Perl,Tcl,Python,Ruby,或者可能是其他更高级的编译型语言,例如C,C++或者是Java。尽管如此,使用Shell脚本来构造应用原型仍然是一个有用的开发步骤。
我们将会使用Bash,它是 “Bourne-Again shell” 的首字母缩写,并且是Stephen Bourne写的经典的Bourne shell的同义词。Bash已经变成了所有令人喜欢的UNIX上shell编程,事实上这本书的大多数脚本技术能很好的应用到其他的Shell当中,比如说Korn Shell,Bash借用了它的一些特性, [2] 还有C Shell和他的不同之处。(注意:由于某些固有问题,不建议使用 C Shell 编程(原文:Note that C Shellprogramming is not recommended due to certain inherent problems),这点已由Tom Christiansen在1993年10月在 Usenet post 被指出了)
接下来是的是一篇脚本的指南。它由许多的例子来引出Shell的许多特性。这些已经被测试过的例子不仅能工作,并且可能的话某些甚至能用在真正的应用中。读者能让源码文件(scriptname.sh或是 scriptname.bash)的这些例子真正地运行起来, [3] 给他们增加运行权限(chmod u+rx [scriptname]
), 然后运行他们看看运行结果。如果你没有源码包,你仍然可以从html, pdf, 或是text 格式版本中复制粘贴代码。注意这些脚本可能在他们被详细解释前提前展示一些特性,这时读者可以暂时忽略这些特性。
如果不是特别注明,后面这些脚本例子是写这本书的作者所写的。
注
[1] 术语内建(builtin ),是指它是Shell内部的特性。
[2] 许多ksh88的属性, 及一小部分的ksh93 中加入的属性已经被增加到了Bash中.
[3] 依照惯例,用户写的Bourne shell脚本应以 .sh
作为扩展名。而系统脚本,比如说放在目录/etc/rc.d
中的脚本则不用这种命名法。
第二章:从一个Sha-Bang(#!)开始
Shell编程就像一个1950年代的自动点唱机… —— Larry Wall
在最简单的情况下,脚本程序不过是存储在一个文件里的系统命令列表。这至少让你执行它 时不必重新按顺序键入相同功能的命令序列。
例子 2-1. cleanup: 一个清空/var/log目录下的日志文件的脚本
# Cleanup
# 必须以root用户运行.
cd /var/log
cat /dev/null > messages
cat /dev/null > wtmp
echo "Logs cleaned up."
(上面cat /dev/null > [文件名]
命令表示清空文件内容)
这没有什么不同寻常的,它不过是一组可以容易地从控制台或xterm(译者注:一种图形虚拟控制台)中顺序键入的命令集。用一种脚本代替这组命令的用处是使你不必每次执行相同任务时都重复地顺序键入它们。脚本变成了一个工具, 并且它也很容易地在一个实际项目被修改或者定制。
例子 2-2. cleanup: 一个改进版的cleanup脚本
#!/bin/bash
# Bash脚本正确的头部.
# Cleanup, 版本 2
# 需要以root运行.
# 如果不是root用户,在此处添加错误信息打印代码和退出代码.
LOG_DIR=/var/log
# 使用变量比使用硬编码(hard-coded)更好。
cd $LOG_DIR
cat /dev/null > messages
cat /dev/null > wtmp
echo "Logs cleaned up."
exit # 这是从一个脚本中退出正确合适的方法
现在它看起来像一个真正的脚本了。但下面我们将做的更好…
例子 2-3. cleanup: 一个上面脚本的增强版,但不能处理错误
#!/bin/bash
# Cleanup, 版本 3
# 注意:
# -------
# 这个脚本使用了相当多的特性,这些我们稍后将会解释.
#
# 到那时,你已经学了这本书的一半了,你将不会再对shell感觉神秘了。
#
LOG_DIR=/var/log
ROOT_UID=0 # 只有用户ID变量$UID值为0的用户才有root权限.
LINES=50 # 默认的行数
E_XCD=66 # 不能进入到目录时的退出代码值
E_NOTROOT=67 # 不是root用户时退出的代码值
# 必须以root用户运行,以下进行检测
if [ "$UID" -ne "$ROOT_UID" ]
then
echo "Must be root to run this script."
exit $E_NOTROOT
fi
if [ -n "$1" ]
# 测试是否提供了命令行参数(即是测试命令行参数至少有一个参数)
then
lines=$1 # ar:创建lines变量,并赋值为$1
else
lines=$LINES # Default, if not specified on command line.
fi
# Stephane Chazelas建议,
#+ 下面是一种更好的检测命令行参数的方法,
#+ 但是对于现在来说还是有些高级。
#
# E_WRONGARGS=65 # 不是数字参数 (参数格式不对)时的退出码
#
# case "$1" in
# "" ) lines=50;;
# *[!0-9]*) echo "Usage: `basename $0` file-to-cleanup"; exit $E_WRONGARGS;;
# * ) lines=$1;;
# esac
#
#* 可以跳到"循环"那章阅读开头一部分去了解上面的代码意思.
cd $LOG_DIR
if [ `pwd` != "$LOG_DIR" ] # 也可以用 if [ "$PWD" != "$LOG_DIR" ]
# 如果工作目录不在/var/log里?
then
echo "Can't change to $LOG_DIR."
exit $E_XCD
fi #在操作清空日志文件之前再次检查是否在正确的目录里
# 可以像下面再次确定是否在正确的目录里:
#
# cd /var/log ||
# echo "Cannot change to necessary directory." >&2
# exit $E_XCD;
#
tail -$lines messages > mesg.temp # 保存message日志文件最后面几行日志信息到临时文件.
mv mesg.temp messages # 然后用临时文件覆盖messages日志文件
# cat /dev/null > messages
#* 上面这句把messages日志文件全部清空,这样没有上面那样保留最后几行安全
cat /dev/null > wtmp # ': > wtmp' and '> wtmp' have the same effect.
echo "Logs cleaned up."
exit 0
#
#一个脚本以0为退出代码表明脚本执行成功.
因为你可能并不希望把整个系统日志都清空,所以这个版本的cleanup保留了日志中最后的几行日志记录。如果你继续努力地学下去,将会发现更多精练的写法来代替上面的代码。
在脚本开头的 sha-bang ( #!)
是告诉系统这个文件是由特定命令解释器解释的一组命令。 那个 #!
实际上是两个字节的 [1] 魔数
, 魔数是指定文件类型的特殊记号,在此是表示这是一个可执行的shell脚本(键入 man magic可了解更多的信息)。紧跟着#!
的是一个路径名.这个路径名是解释这个脚本内命令的命令解释器程序的路径:可能是一个shell,也可能是一个编程语言或者是一个软件包程序。这个命令解释器能执行脚本内的命令语句。它从脚本开头(即从#!
所在行的下一行)起执行,但是忽略注释行。 [2]
#!/bin/sh
#!/bin/bash
#!/usr/bin/perl
#!/usr/bin/tcl
#!/bin/sed -f
#!/usr/awk -f
上面每一个脚本头行都是不同的命令解释器,如果第一行是/bin/sh
, 那就是默认的Shell(Linux系统中bash是默认的shell),否则的话就是其他的解释器. [3] 如果使用#!/bin/sh
,/bin/sh
(因为大多不同的商业UNIX都使用Bourne shell为默认shell)可以使脚本能够移植到非Linux的机器上,虽然这样做你将不能使用Bash许多特有的属性。但这样做的脚本遵循 POSIX [4] sh标准.
值得注意的是,在"#!
"后面提供的路径必须是正确的,否则你运行脚本只会收到通常像"Command not found"那样的错误信息。
如果脚本程序只是由一组普通的系统命令而没有使用Shell内置命令的话#!
将被忽略。上面的第二个例子被要求以#!
开头是因为变量赋值(lines=50),这就使用了Shell的特有的语句。再次提醒使用#!/bin/sh
将调用默认的命令解释器,这在Linux系统上是/bin/bash.(ar:什么意思,没太看懂。。。)
这份指南鼓励使用模块化的方法来写脚本。留意记录像“模板”的代码片断以备将来的脚本使用。最后你能生成一个很好的可扩展的例程库。
例子:测试脚本是否被正确的数目参数调用
下面的代码片断可以测试脚本是否被正确的数目参数调用。
E_WRONG_ARGS=65
script_parameters="-a -h -m -z"
# -a = all, -h = help, etc.
if [ $# -ne $Number_of_expected_args ] //$#表示启动此脚本的命令的参数个数(>=0)
then
echo "Usage: `basename $0` $script_parameters"
# `basename $0`是指脚本名称(译者:这个内容在后面章节会讲).
exit $E_WRONG_ARGS
fi
很多时候,你会写一个执行实际功能的脚本。本章的第一个脚本就是一个例子。以后它可能会使你记起把这个脚本扩展以完成类似的任务。使用变量代替固定的字符串常量是好的办法,像这样的办法还有用函数代替反复使用的代码块。
2.1. 运行脚本
写完一个脚本,你能够运行它用命令:sh scriptname
, [5] 另外也也可以用bash scriptname
. 来执行(不推荐使用:sh <scriptname
, 因为这样会禁止脚本从标准输入里读数据)。更为方便的是你可以使用chmod命令来使脚本自身变为可执行的.
你可以:
chmod 555 scriptname
(使每个人都有读和执行的权限) [6]
也可以:
chmod +rx scriptname
(使每个人都有读和执行的权限)
chmod u+rx scriptname
(仅仅使脚本文件拥有者有读和执行的权限)
在给脚本加上执行权限之后,你可以很容易地使用./scriptname
. [7] 来执行它。如果脚本以"#!
"行开头,将会调用正确的命令解释器来执行它.
最后,把脚本测试并调试完后,如果想把脚本给系统中所有其他的用户使用,你应该把脚本移到目录/usr/local/bin
中(当然,这必须要有root的权限),这样只需简单地在命令行输入scriptname [回车]
就能执行脚本了。
注释:
[1] 一些令人喜爱的UNIX系统(它们都基于4.2BSD)的脚本是四个字节的魔数,在字符”!
”后面要求一个空格 – #! /bin/sh
.
[2] The #!
行将会命令解释器(sh或是bash)在Shell脚本中最先看到的。因为这行以#
字符开头,命令解释器最终执行脚本时将会正确的把这一行当做注释行来对待。这行刚刚好只起到了它调用命令解释器的作用。
事实上,如果脚本包含了额外的#!
行,bash将会把它当作一个注释.
#!/bin/bash
echo "Part 1 of script."
a=1
#!/bin/bash
# 这样不会运行一个新的脚本.
echo "Part 2 of script."
echo $a # Value of $a stays at 1.
[3] 利用上面所述的特性能找到一些有趣的窍门.
#!/bin/rm
# 删除自身的脚本.
# 当你运行这个脚本时,除了这个脚本消失了之外,你不会发现更多其他的东西。
WHATEVER=65 # 这里也运行不到
echo "This line will never print (betcha!)."
exit $WHATEVER # 不要紧,脚本绝不会运行到这儿.
也可以写一个以#!/bin/more
,开头的文件,并执行它。执行结果会发现这是一个自我显示的文件。 (一个使用cat命令的here document 能更好地办到这一点–参考例子 17-3).
[4] 可移植操作系统接口(Portable Operating System Interface),它尝试使类UNIX的操作系统标准化。POSIX的规范可以在站点Open Group site找到.
[5] 注意:以sh scriptname
运行一个Bash脚本将会禁止所有Bash的扩展特性。因此脚本可以会因此而运行失败。
[6] 因为运行一个脚本文件需要Shell能读此文件中的命令,因此不仅需要文件的执行权限,也需要文件有读的权限。
[7] 为什么不直接用简单地键入scriptname
来运行脚本? 如果当前目录(由变量$PWD
)的值保存[译者注:也可由命令pwd打印当前目录路径])就是scriptname 脚本文件所在的目录,为什么直接键入scriptname不能运行脚本? 这样运行脚本可能失败。因为由于安全的因素 [译者注:如果你感兴趣的话,其中一个安全漏洞可在LinuxSir的Shell版中我发的一篇帖子里找到] 当前目录默认不加入到用户的$PATH
.变量中。因此你必须明确地指定调用脚本的路径在当前路径:./scriptname
.
(如图,直接运行test.sh很有可能会引用到其他地方的文件)
2.2. 预备练习
- 系统管理员常常写脚本来自动化日常任务。给出几个实例类似的脚本在哪些地方会有用。.
- 写一个脚本实现显示时间和日期, 列出所有登录系统的用户,并且给出系统的运行时间.最后脚本还会将这些信息写入一个日志文件.
命令杂烩
cat /dev/null > [文件名]
命令表示清空文件内容
以上是关于linux shell编程bash编程shell教程bash教程shell文档bash文档shell脚本bash脚本教程第一部分:绪论的主要内容,如果未能解决你的问题,请参考以下文章
linux shell编程bash编程shell教程bash教程shell文档bash文档shell脚本bash脚本教程第一部分:绪论
linux的shell编程中#!/bin/sh和$bash是啥意思?
Linux bash/sh/shell编程中的if语句应该怎么写