设为首页 收藏本站
查看: 883|回复: 0

使用Bash编写Linux Shell脚本-4.脚本初探

[复制链接]

尚未签到

发表于 2018-8-28 08:58:19 | 显示全部楼层 |阅读模式
  我第一次接触计算机还是在高中的学校内,学校购买了一台崭新的TRS-80,Model I,安装在图书馆内,免费提供所有的人使用,我记得我按照一本手册打印出了我的第一个BASIC程序。
  10 PRINT “KEN WAS HERE”;
  20 GOTO 10
  运行完程序,我兴奋不已。不但计算机如实地按照我的指令一遍又一遍的执行,屏幕还可以自动滚动。屏幕满了,它自动腾出空间给更多的信息,如果信息的长度超出了64个字符,屏幕自动换行,和上一行对齐。这对我那时的年龄来说简直就是神奇的魔术。
  编程的乐趣在于创造一些新东西的乐趣,就像莎士比亚在仲夏夜之梦中所说的即使空无一物也可以有居处和名字。尽管有bug,它们也是以神奇的方法,在一条创新的长路上,一个想象不到的地方竖立了路标,提供你选择要去的地方。一个选择会导致另一个,没有两个程序是完成相同的。创造过程的产物就像油画、歌唱或作诗一样令人乐趣无穷。
  现在,我们要在Bash中开始脚本编程的旅行了。本章将介绍脚本编程的基本技术——建立一个优良结构的脚本,重定向标准文件、处理不同的命令。
建立脚本
  Bash外壳脚本通常以.sh结尾。下面显示了一个脚本称之为hello.sh,它的作用是在屏幕上显示一条脚本。
  # hello.sh
  # This is my first shell script
  printf “Hello!  Bash is wonderful.”
  exit 0
  使用井(“#”)号开始的行是注释。它们只是提示阅读脚本的人,而不影响脚本的执行。井号开头的所有行都将被Bash忽略掉。
  注释应该总是一些信息,脚本要实现的内容的描述,而不是对每一行脚本命令的说明。我看到太多的脚本只有一行注释“Abandon Hope All Ye Who Enter Here”。在商业软件中,清晰的信息说明有助于解决问题并调试晦涩难懂的程序。
  exit命令后面更了一个0,告诉外壳本脚本程序已经成功运行。
  你可以使用下面命令来运行这个新建立的脚本程序:
  $ bash hello.sh
  Hello!  Bash is wonderful.!
  脚本运行正常,Bash显示了一条消息。
建立正确的脚本
  假如你的工作是建立一个脚本程序,目的是系统没有用户时运行命令sync,那么下面的脚本就可以完成上面的工作。
  USERS=`who | wc -l`
  if [ $USERS -eq 0 ] ; then
  sync
  fi
  上面的脚本适合在Bash命令提示符下操作,但是在脚本中要考虑下面的问题:
  n         Linux如何知道这是Bash脚本?
  n         如果有多个who命令执行哪一个?
  n         脚本如何通知外壳程序本脚本是否运行成功?
  n         如果sync命令被意外删除或系统管理员变更了权限后会发生什么事?
  Bash是非常灵活的语言:需要时它可以用于交互。但是在脚本中,这种灵活性会导致安全漏洞和不可预知的行为。一个好的脚本语言需要做的不仅仅是在提示符下简单的执行相同的命令。
  一个好的Bash脚本可以分为下面五部分:
  n         头部
  n         全局声明
  n         完整性检查(sanity check)
  n         主脚本
  n         清除
  上面的每一部分在设计脚本时都扮演着重要的角色。下一步,你再看一眼先前的示例,看看如何根据这五部分来改善它。
头部
  头部定义了脚本是那一种,谁写的,它的版本号是多少,做了什么假定或Bash使用的脚本的选项是什么。
  脚本的第一行称之为头行。这一行以“#!”开始,这一行字符组合标示了脚本的种类。Linux使用这一行信息来选择执行脚本的外壳程序。这一行是一个绝对路径表明Bash解释器的保存位置。大部分Linux发行版中,第一行都是如下所示:
  #!/bin/bash
  如果你不知道Bash外壳的位置,使用Linux的whereis命令可以等到:
  $ whereis bash
  bash: /bin/bash
  下面是一个典型的脚本程序:
  #!/bin/bash
  #
  # Flush disks if nobody is on the computer
  #
  # Ken O. Burtch
  # CVS: $Header$
  shopt -s -o nounset
  头部之后跟着脚本的目的和作者。CVS行将在第8章,“调试和版本控制”中讨论。shopt –s –o nounset命令探测是否有拼写错误并提交报告说是否有未定义的变量。
全局声明
  在脚本顶部的声明适用于整个脚本。在某个地方放置global declarations,阅读者在看脚本时将很容易的明白它们的含义。
  # Global Declarations
  declare -rx SCRIPT=${0##*/}            # SCRIPT is the name of this script
  declare -rx who=”/usr/bin/who”         # the who command - man 1 who
  declare -rx sync=”/bin/sync”           # the sync command - man 1 sync
  declare -rx wc=”/usr/bin/wc”           # the wc command - man 1 wc
完整性检查
  完整性检查保护脚本在计算机上出现意外的更改。通常,一个命令运行在交互方式下,出现了拼写错误,系统找不到该命令会提示你处现了错误。在交互方式下,你可以修改命令并重新提交,可以节省时间。
  但是,脚本运行在没有人进行监视的状态下,在脚本执行任何命令之前,需要检查所有的文件是否可以访问,该命令是否可以执行,是否保存在正确的地方。这种检查称之为完整性检查。除非计算机被认为是正确的状态,否则脚本不能开始主脚本的执行。象Linux这样可以高度自定义的操作系统是特别重要的。在一台计算机上能正确运行的脚本不一定在另一台计算机上也可以。
  另一种完整性检查是进行实时错误检查。大部分错误在语句执行时被捕获。在脚本执行的早期检查危险情况可以保护脚本任务执行中途出现错误,否则你很难决定是否让脚本继续执行还是中断。
  一些系统管理员有时无意识的删除或更改文件的可访问性,可能导致一个脚本无效。有时,环境的变化能更改命令的执行。某些恶意的计算机用户已经知道了如何更改某些人的登陆配置文件,导致你认为正在运行的命令可能不是你实际上使用的那个。
  下面的脚本示例中,你需要效验那些你要使用的命令是否在正确的地方,它们是否在脚本中有效。
  # Sanity checks
  if test -z “$BASH” ; then
  printf “$SCRIPT:$LINENO: please run this script with the BASH shell/n” >&2
  exit 192
  fi
  if test ! -x “$who” ; then
  printf “$SCRIPT:$LINENO: the command $who is not available — aborting/n “ >&2
  exit 192
  fi
  if test ! -x “$sync” ; then
  printf “$SCRIPT:$LINENO: the command $sync is not available — aborting/n “ >&2
  exit 192
  fi
  if test ! -x “$wc” ; then
  printf “$SCRIPT:$LINENO: the command $wc is not available — aborting/n “ >&2
  exit 192
  fi
主脚本
  在你效验了系统的完整性之后,你可以执行你的主任务了。
  # Flush disks if nobody is on the computer
  USERS=`who | wc -l`
  if [ $USERS -eq 0 ] ; then
  sync
  fi
清除
  最后,脚本需要做一些清除工作,例如,删除临时文件,将状态返回给一个人或正在运行的脚本的程序。本例中没有文件需要被清除。在更复杂的脚本中也许使用变量来保存失败返回的状态码。
  通过放置清除段在你的程序末尾处,你只需要一行,而不是放置在脚本的各个地方。
  exit 0  # all is well
  完整的脚本如下所示:
  #!/bin/bash
  #
  # Flush disks if nobody is on the computer
  #
  # Ken O. Burtch
  # CVS: $Header$
  shopt -s -o nounset
  # Global Declarations
  declare -rx SCRIPT=${0##*/}            # SCRIPT is the name of this script
  declare -rx who=”/usr/bin/who”         # the who command - man 1 who
  declare -rx sync=”/bin/sync”           # the sync command - man 1 sync
  declare -rx wc=”/usr/bin/wc”           # the wc command - man 1 wc
  # Sanity checks
  if test -z “$BASH” ; then
  printf “$SCRIPT:$LINENO: please run this script with the BASH shell/n” >&2
  exit 192
  fi
  if test ! -x “$who” ; then
  printf “$SCRIPT:$LINENO: the command $who is not available – aborting/n” >&2
  exit 192
  fi
  if test ! -x “$sync” ; then
  printf “$SCRIPT:$LINENO: the command $sync is not available — /
  aborting/n “ >&2
  exit 192
  fi
  if test ! -x “$wc” ; then
  printf “$SCRIPT:$LINENO: the command $wc is not available — aborting/n “ >&2
  exit 192
  fi
  # Flush disks if nobody is on the computer
  USERS=`$who | $wc -l`
  if [ $USERS -eq 0 ] ; then
  $sync
  fi
  # Cleanup
  exit 0  # all is well
  这个脚本程序比只有四行的主程序长多了。通常,主脚本越长,完美风格脚本的头部越少。这个新脚本比只含主程序的脚本更安全和更可靠。
停止一个脚本
  logout命令用来结束交互式的登录会话,并不能停止脚本(毕竟,脚本不是一个登录会话)。Bash提供了两个内置的命令来终端脚本的执行。
  如前所示,exit命令用于无条件停止一个脚本。exit命令可以包含一个返回状态码给脚本的调用者。状态码0表示没有错误。如果省略了状态码,上一个命令执行的状态由脚本返回。最终,最好是提供一个退出状态码。
  exit 0 # all is well
  脚本到达了结尾自动停止,仿佛隐含了一个exit命令,但是返回的状态码是上一个命令执行的结果。
  suspend命令优点象无条件停止脚本。可是,和exit不同的是,脚本的执行暂停在那,直到有信号唤醒脚本继续执行下去。
  suspend # wait until notified otherwise
  这个命令在第十章详细讨论。
  还有一个命令sleep。sleep命令暂停一个脚本一段时间,然后它自动被唤醒并继续执行下去。
  sleep 5 # wait for 5 seconds
  sleep命令暂停一个脚本,使用户可以读取显示的内容时是特别有用的。但是,sleep不适合同步事件,因为在计算机运行程序多长时间通常取决于系统的负载、用户数、硬件升级情况和许多其他脚本控制之外的因素。
从键盘读取输入
  内置命令read停止脚本的运行并等候用户从键盘输入。输入的文本被分配给和read配对的变量中。
  printf “Archive files for how many days? “
  read ARCHIVE_DAYS
  上面的示例中,变量ARCHIVE_DAYS包含了用户输入的天数。
  read命令有许多选项,例如:“-p(prompt)”是将printf和read命令合并起来的简写形式。read命令可以利用此选项在屏幕上为等候输入的用户显示一条消息。
  read -p “Archive files for how many days? “ ARCHIVE_DAYS
  “-r(raw input)”选项关闭使用斜杠开头的特殊字符的转义功能。通常,read命令可以将命令中的转义字符给正确的解释出来,例如:“/n”解释为换行,使用“-r”选项,read命令不解释斜杠开头的字符,它认为它们是普通字符。只有你需要自己处理斜杠开头的字符时才需要使用“-r”选项。
  read -p “Enter a Microsoft Windows pathname (backslashes allowed): “ -r MS_PATH
  “-e”选项只在交互时工作,在脚本中不起作用。它使你可以使用Bash历史特性来选择返回的行。例如:你能使用上下箭头键来在最近使用的命令中移动。
  退出时间可以使用“-t”开关进行设置。假如到了设定的时间没有输入,脚本将继续下一条命令,变量的值不做更改。如果用户在约定的时间后输入,用户输入的内容将会丢失。推出时间以秒为单位。
  read -t 5 FILENAME # wait up to 5 seconds to read a filename
  如果有一个变量叫TMOUT,即使不用“-t” ,Bash在这个变量的时间到了之后也退出。
  如果使用了“-n”开关,输入的字符将受到限制,如果输入的字符达到了最大的字符数,外壳继续下一条命令, 等候确认键或回车键的按下。
  read -n 10 FILENAME # read no more than 10 characters
  如果你没有提供一个变量,read命令将输入的文本保存在变量REPLY中。好的脚本应避免使用缺省行为,避免脚本的阅读者不清楚REPLY变量来自哪里。
  read命令还有一些特殊的开关,我们将在第13章“控制台脚本”中讨论。从键盘读取输入之后,read命令返回状态码0。
基本的重定向
  你可以将来自命令的消息转移到文件中,就像printf输出到文件之类的命令。Bash将这种行为称之为重定向(redirection)。系统有大量的重定向操作符。
  “>”操作符用来将来自命令的消息重定向到文件。重定向操作符之后跟着消息要保存的文件名。例如:将消息“The processing is complete”保存到results.txt文件中。
  printf “The processing is complete” > results.txt
  “>”操作符总是覆盖要保存的文件。如果几条printf命令将消息重定向到相同的文件中,只有最后一条消息才会出现。
  为了不覆盖原始的信息,Bash还有追加操作符“>>”,这个操作符将消息追加到文件的结尾处。
  printf “The processing is complete” > results.txt
  printf “There were no errors” >> results.txt
  result.txt将包含两条文本:
  The processing is complete
  There were no errors
  同样,输入也可以从文件重定向到命令。这个输入重定向符号是“&2
  这个命令和不使用“>&2”重定向符的结果是相同的,但是,在内部处理流程它们是有很大区别的。它在屏幕上显示错误消息,而不管先前的标准输出重定向到什么地方了。
  标准错误文件也可以重定向。重定向符号和标准输出文件类似,但是不同的是它以2开始。
  $ bash listorders.sh 2> listorders_errors.txt
  上面示例中,所有来自listorders.sh的错误消息保存在listorders_errors.txt中。
  如果ls命令将错误消息直接写入“/dev/tty”的屏幕文件中,没有其他方法将消息重定向到一个分开的文件。
  Linux认为所有的输入来自某个文件。这个特殊的文件称之为标准输入文件——standard input。可以使用符号“&0”来表示,也可以使用“/dev/stdin”表示。当命令使用“|”符号链接在一起,第一个命令的标准输出文件成为第二个文件的标准输入。
  重定向可以被合并为一个单独的命令。它们的顺序是非常重要的,重定向的处理顺序为从左到右。重定向标准输出和标准错误到一个单独的文件,下列命令将起到作用。
  $ bash listorders.sh > listorders_errors.txt >&2
  可是,将重定向的顺序翻转并不能正常工作,因为它重定向错误到一个旧的标准输出接着重定向标准输出到文件listorders_errors.txt。
  因为将标准输入和标准错误重定向是如此频繁,以至Bash提供了一个短符号“&>”将两者都重定向。
  $ bash listorders.sh &> listorders_errors.txt
内置命令对linux命令
  原始的Bourne外壳被设计为除非是必要的,否则由外壳外部的程序来实现。即使是算法也必须由外部程序来实现。这种设计使得Bourne外壳程序非常灵活,但是也带来了两个缺点。第一:因为即使是最简单的任务也要将程序加载进来,重新执行,使得系统太慢。第二:不能保证一个命令在一个系统有效也能在另一个系统有效,使得外壳脚本很难移植。
  为了处理这些问题,Bash有许多内置的命令。除了和旧的Bourne外壳做基本的兼容,Linux任然有它自己的Bash命令的版本。例如:test是一个Bash内置的命令,但是Linux也有自己的程序——“/usr/bin/test”提供给外壳使用,以便在外壳不提供test命令时使用。
  如果你不知道某个命令是否是内置的,Bash的type命令将会告诉你。如果命令是Linux命令,他将显示命令的路径(象whereis命令一样)。
  $ type cd
  cd is a shell builtin

  $ type>  id is /usr/bin/id
  “-t”开关显示命令的类型。“-p(路径)”开关显示此命令的哈希表的值,“-a”开关列出所有的命令情况。
  $ type -a pwd
  pwd is a shell builtin
  pwd is /bin/pwd.
  有两个开关可以限制查找的范围“-f”开关不检查在Bash内声明的功能。“-P”开关进一步检查Linux命令的路径并忽略Bash命令、功能或别名。
  bulitin命令显示的运行一个内置的命令。即使有一个相同名字的别名也照样运行。
  builtin pwd
  通用command命令显示的运行一个Linux命令,即使有相同名字的内置命令huo别名也运行。
  command pwd
  使用“-v”或“-V”开关,command显示关于命令的信息,例如它的路径名。使用“-p”开关,command命令查找标准的Linux中bin库;当你更改了PATH变量时,这是非常有用的。
  一份完整的内置外壳命令列表附在附录B中。
  虽然builtin和command命令在测试和移植老的脚本到Bash时是有用的,但是有良好结构的脚本并不依赖于它们,它们在脚本设计时需要这种模糊性。
  内置命令enable可以临时隐藏内置命令并在以后可以再开启。“-n”开关关闭命令。
  $ enable test
  $ type test
  test is a shell builtin
  $ enable -n test
  $ type test
  test is /usr/bin/test
  “-d”开关关闭内置命令。你可以将“-p”和“-n”两个开关一起使用,用来显示已关闭的内置命令,或使用“-a”开关显示所有。“-s”开关限制列表为POSIX指定的内置命令。
  $ enable -pn
  enable -n test
  在一个由优良的脚本中,enable应该使用在全局声明段中。在整个脚本中使用enable命令,很难记住某个特定的命令是否为内置命令。
  Set和Shopt命令。
  Bash有许多选项特性是可以打开或关闭的。这些选项是关于Bash如何和用户进行交互的、它在脚本中的行为是什么、它是Bash如何和其他外壳和标准进行兼容的。
  当Bash开始运行时,Bash选项可以通过命令开关进行设置。例如:开始一个Bash会话并不允许使用未定义的变量时使用下面命令:
  $ bash -o nounset
  在脚本中或Bash的美元提示符下,你可以禁止使用未定义变量使用下面的命令:
  $ shopt -s -o nounset
  以前,使用set命令打开或关闭选项,随着选项的数量的增加,set命令因为选项使用单个字符表示变得很难使用,最终,Bash提供了shopt(shell options外壳选项)命令来对选项进行控制。你可以只使用字母来设置选项。而其他的由shopt命令提供,因此使的查找和设置某个选项变得让人头昏目眩。
  “shopt –s(set)”命令打开一个外壳选项。“shell –u(unset)”命令关闭一个选项。不使用“-s”或“-u”,shopt切换当前的设置。
  $ shopt -u -o nounset
  shopt本身或加上 “–p”开关将显示选项的列表并显示它们是否已经打开了,“-o”选项不包含在内。为了看到“-o”选项,你需要设置“-o”。一个字母代码的列表保存在外壳变量“$-”中。
  这些开关的大部分在特定的环境中是有效的。例如在UUCP网络,感叹号“!”用在电子邮件中,但是Bash使用感叹号用于历史命令的处理。历史处理可以使用下面命令关闭:
  $ shopt -u -o histexpand
  关闭了历史命令的使用,UUCP电子邮件地址可以用于命令行的交互。完事之后,可以再将历史命令的使用打开。
命令参考
  comman命令开关
  n         -p——在标准的Linux执行文件库中查找命令
  n         -v——命令的解释
  n         -V——详细解释命令
  enable命令开关
  n         -a——无论内置命令是否打开,显示所有的内置命令
  n         -d——关闭并删除内置命令
  n         -f file——打开并加载内置命令
  n         -n——关闭内置命令
  n         -p——显示内置命令列表
  n         -s——限制结果为指定的POSIX内置命令
  read命令开关
  n         -a array——读取文本到数组中
  n         -d d——遇到字符d停止读取而不是读取一行
  n         -e——使用交互式编辑
  n         -n num——读取num个字符数而不是整行文本
  n         -p prompt——显示一个提示
  n         -r——显示源输入
  n         -s——隐藏输入的字符
  n         -t sec——在sec秒后退出
  suspend命令开关
  n         -f——即使是登陆外壳也要让脚本挂起暂停运行


运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-557536-1-1.html 上篇帖子: linux shell和Expect逝去的事情简介 下篇帖子: 使用Bash编写Linux Shell脚本-2.使用外壳语言
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表