了解对本教程的期望以及如何最大程度地利用本教程。
该系列教程简要介绍了UNIX®基本概念,并针对新用户的角度进行了编写。 该系列中的前三篇教程提供了针对MicrosoftWindows®背景的新用户的UNIX系统概述,描述了文件系统和常用命令,介绍了vi (最广泛使用的UNIX编辑器),以及使用grep , sed和awk工具快速了解过滤器和正则表达式。
本教程提供了一些壳技巧和技巧的集合,这些技巧和技巧对于新用户来说很方便。 它显示了如何使用Bourne Shell中编写的小脚本自动执行特殊情况,包括自动基本转换,读取键盘输入,在子Shell中执行命令,对目录中所有文件执行命令以及各种形式的循环。 本教程以一组有用的外壳一线结束。
本教程的目的是向新用户展示如何使用和实现许多Shell方法来提供不同级别的自动化。 它通过提供特殊情况的技巧和技巧来演示这些方法,并且还提供了用于常见任务的有用的外壳单层衬套。
本教程是为UNIX较新的用户编写的。 唯一的先决条件是UNIX文件系统的基本知识以及操作该文件的命令,命令行本身以及使用诸如vi类的编辑器来编辑文本文件。 所有这些概念在本系列以前的教程中都有详细介绍。
您需要使用兼容Bourne的shell环境(例如流行的bash shell)对UNIX系统进行用户级访问。 这是本教程的唯一系统要求。
学习shell脚本的最佳方法是通过示例。 您将在脚本中执行的任何命令都可以在命令行中直接尝试,这就是本教程中提供的许多动手示例。 例如, echo命令将一行文本写入标准输出。 (许多外壳程序提供它们自己的echo版本作为内置命令,包括Bourne shell的IBMAIX®实现。如果您是这种情况,那么在运行echo ,实际上是外壳程序正在运行的命令版本。 )
尝试通过将短消息括在引号中来输出带有echo的短消息:
$ echo "Hello, world" Hello, world无论是在命令行还是在脚本中,Shell 引号都是将字符串传递给Shell的一种方式,这样字符串就可能不包含任何特殊的元字符。 当字符串包含多个单词时,可以使用引号并将一个包含空格的短语括起来。 当一个字符正好是一个外壳元字符并且想要取消使用它的特殊含义时(例如,当您希望将美元符号( $ )作为字面的美元符号字符而不是前面的特殊元字符传递时,您也要引用单个字符)变量名。
引用文本内会发生各种扩展 。 例如,在双引号文本内部,变量将扩展为它们的值,而在单引号文本内部进行引用时,文字变量名称不会扩展。
需要了解三种重要的报价类型:
在单个字符前加反斜杠(\)来引用该字符。 这将传递文字字符,而不传递其可能具有的任何特殊含义,例如空格字符或shell元字符。 例如,要引用星号(*)(它是一个外壳元字符),请使用\* 。 要引用实际的反斜杠字符,请使用\\ 。
通过将文本字符串括在双引号( " )中来传递扩展的引号 。美元符号( $ )和单引号( ' )字符保持其含义。因此,除其他外,引号中引用的任何变量名都将被替换。换行之前的反斜杠或某些字符( $`"\ )将被删除,但它们引用的字符将被传递。
通过将文本括在单引号( ' )中,传递文本字符串的文字引号 (传递所有变量名,元字符等作为字符本身,而不传递其含义或值)。
请注意,不同的外壳使用不同的报价规则。 请查阅您特定外壳的man页,以了解其确切规则。
尝试分配一个变量,然后以各种引号样式输出它,如清单1所示。
请注意,根据所使用的引用样式,如何用不同的方式解释变量。
在外壳程序中,井号( # )开始注释行。 散列及其后面的所有内容都将被忽略。 尝试键入插入注释的行,如清单2所示:
如您所见,您可以直接在命令行上测试这些Shell编程结构。 但是,当您毕业于单行命令之外而实际上开始编写更长的程序时,则需要将它们写入称为脚本的文件中。 脚本是一个文本文件,具有可执行位,并且包含一个由外壳语言的命令组成的程序。 UNIX shell是一种解释语言 ,这意味着它的程序不是编译的而是由解释器读取的,解释器是shell可执行文件本身,例如/bin/sh , /bin/bsh或/bin/bash 。
Shell脚本的第一行始终是相同的:
#!/bin/sh这是外壳程序自己用来确定文件语言或内容的特殊注释行。 感叹号(在UNIX中通常称为bang和排版习惯用语),后跟路径名指示外壳程序应使用解释器来执行文件。 在这种情况下,它是/bin/sh ,在许多系统上,它是Bourne shell可执行文件本身。 例如,专门为Korn shell编写的脚本可能以#!/usr/bin/ksh行开头,就像Ruby脚本以#!/usr/bin/ruby开头一样。 安装bash , /bin/sh通常是指向bash二进制文件的符号链接。 并且,出于兼容性考虑,使用/bin/sh比使用/bin/bash更可取。 在某些系统上,例如IBM AIX 5L™,Bourne Shell可执行文件的名称为bsh ,位于/ usr / bin / bsh中。
清单3给出了一个简短的示例shell脚本。
使用vi进行键入,并将其保存到名为myscript的文件中,如本系列先前的教程中所述(请参阅参考资料 )。 然后,使用chmod通过在文件上设置执行许可权使其可执行:
$ chmod u+x myscript此命令使它只能由您执行。 如果要让系统上的所有用户运行它,则可以始终为所有用户设置执行权限:
$ chmod a+x myscript现在您可以运行它。 给出与当前工作目录相关的文件名,该文件名在路径中以点字符( . )指定:
$ ./myscript The message is 'Hello, world!' $shell变量PATH包含由冒号分隔的目录列表。 据说这是您的路径 ,并且外壳程序始终“查看”这些目录中的任何文件。 UNIX路径的目的是使运行二进制文件变得很方便。 这就是为什么您只键入命令的基本文件名,例如ls和echo ,而不是给出它们的完整或相对路径名的原因。 如果将脚本移动到路径上的目录,则只需键入其名称即可运行它。 确切的路径取决于您的UNIX实现和本地设置,但是通常该路径上的目录包括/ bin,/ usr / bin和/ usr / local / bin。
一些用户配置其shell,以便PATH变量包含当前工作目录,该目录在路径中以点字符(“ . ”)指定。 这样,要在当前目录中运行脚本,只需键入其名称,而无需指定相对目录。 Shell按照给定的顺序沿着路径搜索; 因此,为了防止特洛伊木马或意外事故,将当前工作目录放在路径末尾的任何位置都是不明智的。
要查看路径,请使用echo来显示PATH变量的内容,如清单4所示。
特殊选项或标志可以跟随解释器的名称,例如/usr/bin/bsh -n ,用于调试目的。 连字符可以关闭选项,而加号可以打开选项。 特殊的内置环境变量- (连字符)包含当前shell的选项的完整列表。
尝试显示在当前的交互式外壳程序中设置了哪些选项。 通过使用echo显示-变量的内容来执行此操作:
$ echo $- himBH $请查阅您的Shell man页以获取标志和选项的最新列表。 表1列出了AIX®上Bourne shell的通用标志,以及每个标志的简要说明。
Shell提供了许多基本的算术运算,这些运算在脚本中很有用。 外壳程序对您提供的算术表达式进行求值,然后执行算术扩展 ,然后用结果替换该表达式。 以这种形式给出算术表达式:
$(( expression )通过使用echo在命令行上显示结果,您可以看到正在工作的算术扩展。 现在尝试清单5中显示的内容。
您还可以将扩展分配给变量。 尝试清单6中显示的内容 。
表2列出了在大多数Bourne和Bourne兼容shell中的表达式之间可能使用的一些有效运算符。 与上面的第二个示例一样,分组在其自己的括号中的语句具有优先权。 实际上,shell算术优先级通常是根据C语言的规则确定的。
假设您有一些数字,但是在脚本中,您需要在另一个基础上进行处理。 使用Shell算术可以轻松自动完成此转换。 一种方法是使用Shell算术将数字从给定的底数转换为十进制。 如果数字以算术扩展形式给出,则除非以零开头( 否则假定为八进制或0x), 否则将假定为十进制表示形式。十六进制。 键入以下命令以获取某些八进制和十六进制值的十进制输出:
$ echo $((013)) $ echo $((0xA4))您还可以使用以下格式指定2到64之间的任意基数:
$((BASE#NUMBER))尝试通过在shell提示符下键入清单7中所示的行, 将二进制,八进制,十六进制和其他基数的数字转换为十进制。
在shell中进行基本转换的另一个技巧是使用bc (任意精度计算器语言),该语言在大多数UNIX安装中都可用。 因为它允许您指定输出基数,所以当您需要使用非十进制的输出时,这是一种很好的技术。
特殊的bc变量ibase和obase包含用于输入和输出的基准值。 默认情况下,两者都设置为10。要执行基本转换,请转换它们中的一个或两个,然后给它一个数字。 现在尝试一下,如清单8所示。
要进行快速的基数转换,请将bc与echo结合使用以进行快速的bc转换,将给定值传递给bc 。 输入清单9中显示的内容 。
注意事项:在bc设置输入基数之后,输入到bc所有数字都将以该基数为单位,包括您提供的用于设置输出基数的任何数字。 因此,最好先设置输出基准,否则可能会得到意外的结果,如清单10所示。
虽然echo技巧可通过管道传递给交互式命令(例如bc ,从而在命令行中提供快速的单行代码,但对于多行输入(例如,当可能使用实际文件的内容时)并不实用。 但是还有另一种有用的方法。 该外壳程序具有一种称为here document或内联输入的功能,这是一种动态构建文件的好方法,例如在脚本内部,并将该文件的内容重定向到命令。
使用shell <<运算符指定一个here文档,并在其后一行加上一个限制字符串 ,该字符串是标记输入终止的字符串,可以是您选择的任何文本,只要它是一个没有空格字符的单词。 紧随其后的是构成输入文件的输入行,并在其自己的行上以限制字符串终止输入-它之前或之后不能包含任何文本,或者该行被认为是其中的一部分输入。 与cat一起尝试,如清单11所示。
限制字符串(在这种情况下为END )可以出现在输入中的任何位置-仅当它出现在自己的行中且没有空格或其他字符时,它才用作输入的终止。
内联输入通常在脚本中使用,以将使用信息输出到标准输出。 通常通过将here文档发送到cat ,如清单12中的脚本所示 。 使用vi进行键入,然后将其保存到名为baseconv的文件中,并使该文件可执行(请参见制作shell脚本部分)。
执行脚本后,将here文档的内容发送到标准输出(使用cat )。 现在尝试一下,如清单13所示。
此外,Bourne Shell的大多数实现都可以识别使用可选的连字符重定向的内联输入。 可选的连字符可从所有输入行的开头以及包含限制字符串本身的行中删除所有前导制表符。 在编写要保留当前缩进的脚本时,这很有用。 因为内联输入通常是从字面上获取的,并且必须在行的开头给出限制字符串,所以输入将破坏当前的缩进并使脚本看起来不美观。 因此,您可以重写清单12中的脚本以匹配清单14 ,并且输出将是相同的。
在命令行中,内联输入与调用交互式程序的单行代码一起使用,例如在使用bc进行基础转换部分中讨论的bc计算器。 您可以使用here文档替代任何交互式命令的真实文件或真实输入的任意行。
尝试使用here文档将多行输入发送到bc 。 输入清单15中显示的内容 。
通常使用内联输入扩展变量。 尝试清单16中显示的内容 。
可以在称为subshell的新shell中执行命令或命令列表,其父级是当前shell。 子外壳采用其父级的环境。 I / O重定向可以在子外壳和父外壳之间发生,但是子外壳永远不能修改父环境。 当您想要更改执行这些命令的外壳环境(例如通过设置变量),但又不想更改脚本本身正在运行的环境时,这是理想的。当您想要运行子外壳时,也希望使多个长时间运行的进程同时在后台启动并运行。 一个外壳程序可以产生多个子外壳程序,而这些子外壳程序又可以递归地产生任何数量的自己的子外壳程序。 图1说明了该过程。
Shell有时会自动生成自己的子Shell,例如在管道上使用内置插件时。 在子外壳程序中时,shell $参数将扩展为父外壳程序的进程ID(PID),而不是子外壳程序的。
要在子shell中运行一组命令,请将其括在括号中。 您可以使用重定向将输入发送到子Shell的标准输入,或将其集体输出发送到文件或管道。
尝试在主目录中键入清单17中显示的内容。 如果您还没有名为example的目录,它将创建一个示例目录和一些测试文件。
在此示例中,外壳程序生成一个在后台运行的子外壳程序,创建示例目录并使用touch在该目录中创建三个虚拟文件。 同时,shell返回到主目录中的提示符。
当您有一组需要花费很长时间才能执行的命令时,子命令行在命令行和脚本中都很方便。 要使外壳自由,可以在后台运行它,或在后台运行其中的许多命令:
( group-of-long-running-commands ) & ( another-group-of-long-running-commands ) & ( yet-another-group-of-long-running-commands ) &重要的是要了解变量如何与子外壳一起使用。 由于subshell环境是其父级环境的副本,因此它将继承父级的所有变量。 但是父外壳程序永远不会看到在子外壳程序环境中所做的任何更改,而子外壳程序再也不会看到在生成子外壳程序之后在父外壳程序中所做的任何更改。
例如,使用vi编辑器将清单18中的脚本保存到您的主目录中的名为vartest的文件中,并使其可执行(请参见“ 制作shell脚本”部分)。
现在,尝试通过键入脚本名称来执行该脚本,如清单19所示。
现在该看一下循环了,循环使您可以执行迭代任务,例如对一组文件执行某些操作或命令。 外壳程序有几种构造循环的方法。
最常见的循环构造是for循环。 它首先定义一个变量来命名循环,给出一个成员列表,该成员列表可以是任何单词,包括整数和文件名,然后给出要在每次迭代中执行的命令。 每个命令都以分号(;)终止,整个分组都用do和done括起来。 清单20中描述了它的结构。
在循环的第一次迭代中, loopname变量采用第一个成员的值。 然后,将loopname的值替换为列表中下一个成员的值,并继续进行此迭代,直到没有更多成员为止。
在大多数shell中, do和done可以用大括号代替,如清单21所示。
输入清单22中的文本以运行一个由三个成员组成的简单循环:
您可以使用循环在一组给定的文件上执行一个命令或一组命令。 如果您将文件名指定for循环的成员,则循环将按照您赋予它们的顺序对每个文件进行操作。 您可以给同一个文件两次,然后循环依次对其进行操作。 使用清单23中的文本在示例目录中进行尝试。
要对目录中的每个文件执行操作,请使用星号( * )作为循环的唯一成员,如清单24所示。 Shell将其扩展到目录中的所有文件。 然后,对于希望对所有文件进行操作的循环内命令,请使用loopname变量作为适当的参数或选项。
如果您一直在运行本教程中的所有示例,则应更改示例目录的内容:
$ ls 10 11 12 $发生的是循环中的mv命令将文件名从十六进制值(通过在名称开头插入0x构成)更改为十进制等效值。
您可以使循环运行的时间只要满足某些条件即可。 使用while条件执行此操作, 清单25中描述了它的格式。
在循环中, 条件可以是使用运算符构建的语句(请参见表3 ),也可以像变量名一样简单。 只要该值不为零,就可以。
在构造while循环时,需要牢记一些注意事项。 首先, 条件与其周围的方括号之间必须始终有空白 。 其次,如果将变量用于条件中的数字比较, 则必须先在while语句之前定义该变量 。
输入清单26中的文本以执行简短的while循环:
until条件与while类似,并且使用相同的运算符,但相反。 仅在条件为假时才执行循环,并且循环将继续进行迭代, 直到给定条件变为真为止 。 清单27中描述了它的格式。
通过输入清单28中的文本,尝试运行一个short until循环:
您可以嵌套循环并组合各种循环以执行各种复杂的操作。 由于for循环的元素不必是数字,也不是按时间顺序排列,因此您可以使用命令名称稍后在某些内部循环中作为命令执行,例如printf , echo , stop , resume等。
尝试运行清单29中的示例。 这是一个until嵌套在for循环中(循环词不是数字顺序)时执行算术替换的until循环。
您也可以在脚本中或从命令行本身读取键盘输入。 这是通过read命令完成的, read命令是一个内置函数,它将任意数量的变量名作为参数。 它从标准输入中读取值到变量中,读取单行输入并将每个输入字分配给每个变量。
尝试读取一个变量,如清单30所示:
使用-p选项为每次读取提示。 将提示输入为带引号的字符串,如清单31所示。 发生变量扩展。
如果键盘输入中的单词多于变量,则依次为变量分配输入单词,直到到达最后一个变量,然后最后一个变量被分配给输入行的其余部分。 (如果输入中给出的单词少于变量,那么将为变量分配值,直到分配了所有输入,然后为所有其余变量赋空值。)
您可以将read用作循环中的条件。 现在使用清单32尝试一下:
当对循环的输入进行管道传输时,通常使用此技术。 尝试键入清单33中的文本,该文本将ls命令的输出替换为循环:
您还可以在多行上对该变量进行操作,例如向标准输出发送消息,还可以对loopname变量执行Shell算术(请参见Shell算术和基数转换部分)。 尝试清单34中提供的示例:
您可以通过一次管道read来读取多个值,如清单35所示。
本章的最后部分结合了您刚刚为一些有用的,现实生活中的单线而学到的各种技巧和技术。 它还包括一个简单的shell脚本,该脚本执行任意基本转换。
以下示例是执行有用功能的示例shell一线式。 它们全部由本教程中描述的各种构造组成。
在当前目录中获取一组文件,它们的长度恰好是两个字符,并以.ppm扩展名重命名:
for i in ??; { mv $i $i.ppm; }使用tar和子外壳程序复制整个目录树,同时保留所有相同的文件权限:
( cd source ; tar pcf - * ) | ( cd target ; tar pxvf - )读取二进制数并以十进制输出值:
read BINLOC;echo $((2#$BINLOC))在/ usr / local目录树中找到所有扩展名为.mp3的文件-这些文件的名称中可能带有空格-并使用bzip2实用程序对其进行压缩:
find /usr/local -name "*.mp3" | while read name ; do bzip2 $name; done将给定文件中的所有十进制数字输出为它们的十六进制值:
cat file | while read number ; do echo $((0x$number)); done将给定文件中的所有十进制数字转换为十六进制值,并将其输出到扩展名为.hex的新文件中:
cat file | while read number ; do echo $((0x$number)) >> file.hex; done循环执行十次迭代,运行运行命令,并以数字(从0到90,以十为增量)作为参数传递:
i=0; while [ $i -ne 100 ]; do command $i; i=$(($i+10)); done清单36将本教程中讨论的各种技巧组合在一起。 这是一个简单的脚本baseconv ,它将数字从给定的输入库转换为输出库。 给它输入和输出基数的值作为参数,它从键盘输入中读取数字,直到得到数字0。
将其保存在可执行文件中后(请参见“ 制作shell脚本”部分),尝试运行它,如清单37所示:
ew! 本教程肯定涵盖了很多基础知识,带您纵览了许多基本的Shell编程概念。 在学习本教程的同时,您学习了Shell编程的许多核心概念:连续循环,内联输入,读取键盘输入,基本转换和子Shell执行。 您还了解了如何直接从Shell提示符下以单一代码的形式运行Shell代码片段,以及如何将它们作为可执行脚本放入文件中。 这些是您可以学习的一些最重要的Shell脚本概念。 如果您使用本教程中学到的知识以及本系列先前教程中学到的知识,那么您就很容易成为UNIX专业人士。
翻译自: https://www.ibm.com/developerworks/aix/tutorials/au-unixtips4/index.html
相关资源:微信小程序源码-合集6.rar