如果您在长时间工作的UNIX®用户的肩膀上凝视着他或她,那么您可能会对命令行中低调的奇怪咒语着迷。 如果您已经阅读了Speaking UNIX系列中的任何以前的文章(请参阅参考资料 ),则至少输入了一些神秘的符文-例如波浪号(〜),竖线(|),变量和重定向( <
和>
)-看起来很熟悉。 您可能还会识别某些UNIX命令名称和组合,或者意识到何时将别名用作巫师的速记。
但是,其他命令行选项可能会让您难以理解,因为经验丰富的UNIX用户通常会以外壳程序脚本的形式聚集大量的小型,高度专业化的咒语,以简化或自动执行经常重复的任务。 Shell脚本可以使工作机械化,而不是键入和重新键入(可能)复杂的一系列命令来完成琐事。
在Speaking UNIX系列的第6部分(请参阅参考资料 )中,您将学习如何编写Shell脚本和更多命令行技巧。
本,只有一个词:“自动化”
一些Shell脚本运行完全相同的命令,一次又一次处理相同的文件集。 例如,一个Z shell脚本将您的主目录的全部内容传播到三台远程计算机,就像清单1一样简单。
清单1.一个简单的shell脚本可以在许多远程机器之间同步您的主目录
#! /bin/zsh
for each machine (groucho chico harpo)
rsync -e ssh --times --perms --recursive --delete $HOME $machine:
end
要将清单1用作shell脚本,请将上面的内容保存到一个文件(例如simpleprop.zsh)中,然后运行chmod +x simpleprop.zsh
使该文件可执行。 您可以通过键入./simpleprop.zsh
来运行脚本。
如果您想了解Z shell如何扩展每个命令,请将-x
选项添加到#!
的末尾#!
脚本的(八度感叹号对通常称为shuh-bang )行,如下所示:
#! /bin/zsh -x
对于每台计算机groucho
, chico
和harpo
,脚本都会运行rsync
命令,将$HOME
替换$HOME
您的主目录(例如,/ home / joe),并将$machine
替换为计算机名。
如清单1所示 ,变量和脚本控制结构(例如循环)使脚本更易于编写和维护。 如果您要在池中添加第四台计算机(例如zeppo) ,只需将其添加到列表中即可。 如果必须更改rsync
命令(例如,要添加另一个选项),则只需要编辑一个实例。 与传统编程一样,您也应努力避免在Shell脚本中进行剪切和粘贴。
提出一个很好的论据
其他外壳程序脚本需要参数或动态列表(文件,目录,计算机名称)进行处理。 作为示例,请考虑清单2 ,它是上一个示例的变体,它允许您使用命令行来命名要与之同步的计算机。
清单2.清单1的变体,使您可以命名要处理的计算机
#! /bin/zsh
for each machine
rsync -e ssh --times --perms --recursive --delete $HOME $machine:
end
假设您将清单2保存在名为synch.zsh的文件中,则将脚本称为zsh synch.zsh moe larry curly
调用以将主目录复制到计算机moe,larry和curl。
foreach
行上缺少的列表不是错字:如果省略列表,则foreach
结构将处理命令行中给出的参数列表。 命令行参数也称为位置参数,因为参数在命令行上的位置通常在语义上很重要。
例如, 清单2可以利用位置参数的存在或不存在来提供有用的用法消息(如果您未指定任何参数)。 增强的脚本如清单3所示。
清单3.如果没有提供参数,许多脚本会提供有用的消息
#! /bin/zsh
if [[ -z $1 || $1 == "--help" ]]
then
echo "usage: $0 machine [machine ...]
fi
foreach machine
rsync -e ssh --times --perms --recursive --delete $HOME $machine:
end
命令行上每个以空格分隔的字符串都将成为一个位置参数,包括要调用的脚本的名称。 因此,命令synch.zsh
仅具有一个位置参数$0
。 synch.zsh --help
命令有两个: $0
和$1
,其中$1
是字符串--help
。
因此, 清单3表示:“如果第一个位置参数为空( -z
运算符测试一个空字符串),或者(如果由||
表示)第一个参数等于'--help',则打印用法消息”。 (如果您开始编写脚本,请考虑在每个脚本中提供一个使用情况消息作为提示。它会提醒其他人-甚至您,如果您忘记了-都将如何使用该脚本。)
短语[[ -z $1 || $1 == "--help" ]]
[[ -z $1 || $1 == "--help" ]]
是if
语句的条件 ,但是您也可以使用与命令相同的条件,并将其与其他命令组合以控制脚本中的流程。 看一下清单4 。 它枚举$PATH
所有可执行命令,并结合使用条件和其他命令来执行适当的工作。
清单4.在$ PATH中列出命令
#! /bin/zsh
directories=(`echo $PATH | column -s ':' -t`)
for directory in $directories
do
[[ -d $directory ]] || continue
pushd "$directory"
for file in *
do
[[ -x $file && ! -d $file ]] || continue
echo $file
done
popd
done | sort | uniq
脚本中有很多事情要做,所以让我们将其分解为几部分:
- 脚本的第一行实际内容
directories=(`echo $PATH | column -s ':' -t`)
-创建一个命名目录数组。 您可以在zsh
通过在参数周围加上括号来创建一个数组,如directories=(
...)
。 在这种情况下,数组的元素是通过在每个冒号(column -s ':'
)处拆分$PATH
来生成的,以产生以空格分隔的目录列表(column
的-t
参数)。 - 对于列表中的每个目录,脚本都会尝试枚举目录中的可执行文件。 步骤3至6描述了该过程。
-
[[ -d $directory ]] || continue
[[ -d $directory ]] || continue
行是所谓short-circuiting
命令的示例。short-circuiting
命令“一旦”其逻辑条件产生确定的结果就终止。例如,
[[ -d $directory ]] || continue
[[ -d $directory ]] || continue
短语使用逻辑OR(||
)-当且仅当第一个命令失败时,它执行第一个命令并执行第二个命令。 因此,如果$directory
的条目存在并且是目录(-d
运算符),则测试成功,评估结束,并且跳过当前元素处理的continue
命令将永远不会执行。但是,如果第一个测试失败,则执行逻辑或
continue
的下一个条件。 (continue
总是成功的,因此它通常出现在short-circuiting
命令的最后。)基于逻辑与(
&&
)Short-circuiting
执行第一个命令,然后(且仅当第一个命令成功时)才执行第二个命令。 -
pushd
和随附的popd
分别用于在处理之前更改为新目录和在处理之后更改为先前目录。 使用目录堆栈是一种很好的脚本编写技术,可以保持您在文件系统中的位置。 - 内部的
for
循环枚举当前工作目录中的所有文件(通配符*
(星号)匹配所有内容),然后测试每个条目是否为文件。 该行[[ -x $file && ! -d $file ]] || continue
[[ -x $file && ! -d $file ]] || continue
[[ -x $file && ! -d $file ]] || continue
说:“如果$file
存在且可执行并且不是目录,则对其进行处理;否则,continue
。” - 最后,如果所有以前的条件都满足,则文件名将显示为
echo
。 - 您是否抓住了脚本的最后一行? 您可以将大多数控制结构的输出发送到另一个UNIX命令-毕竟,shell将控制结构视为命令。 因此,整个脚本的输出通过
sort
管道传输,然后通过uniq
管道生成$PATH
找到的唯一命令的字母顺序列表。
如果将清单4保存到名为listcmds.zsh的可执行文件,则输出可能如下所示:
$ ./listcmds.zsh
[
a2p
ab
ac
accept
accton
aclocal
short-circuiting
命令在脚本中非常有用。 它将条件和操作合二为一。 而且,由于每个UNIX命令都返回反映成功或失败的状态代码,因此您可以将任何命令都用作conditional
命令,而不仅仅是测试操作员。 按照约定,UNIX命令成功返回零(0),失败则返回非零,其中非零值反映发生的错误的种类。
例如,如果行[[ -d $directory ]] || continue
可以从清单4中删除pushd
和popd
[[ -d $directory ]] || continue
被替换为cd $directory || continue
cd $directory || continue
。 如果cd
命令成功,它将返回0,并且逻辑或的评估可以立即结束。 但是,如果cd
失败,它将返回非零值,评估继续进行并continue
执行。
不要删除。 存档!
现代UNIX shell( bash
, ksh
, zsh
)提供了许多控制结构和操作来创建复杂的脚本。 因为您可以调用所有UNIX命令来将数据从一种形式转换为另一种形式,所以Shell脚本几乎与使用完整语言(例如C
或Perl)进行编程一样丰富。
您可以使用脚本来机械化几乎任何个人或系统任务。 脚本可以监视,存档,更新,上载,下载和转换数据。 脚本可以是一行,也可以是一个庞大的子系统。 对于Shell脚本,没有任何事情太小或太大(几乎)。 确实,如果查看/etc/init.d目录,则会发现各种Shell脚本,这些脚本在每次启动计算机时都会启动服务。 如果创建一个非常有用的脚本,甚至可以将其部署为系统范围的实用程序。 只需将其放入用户$PATH
上的目录中即可。
让我们创建一个实用程序来练习新发现的mojo。 脚本myrm替代了系统自身的rm。 myrm不会直接删除文件,而是将文件复制到存档中,对其进行唯一命名,以便以后可以找到它,然后删除原始文件。 myrm脚本功能强大但很简单,您可以添加许多功能。 您还可以将大量的unrm(“删除”)脚本作为辅助脚本来编写。 (您可以搜索Internet来找到各种实现。)
清单5中显示了myrm脚本。
清单5.一个简单的实用程序,用于在文件从文件系统中删除之前进行备份
#! /bin/zsh
backupdir=$HOME/.tomb
systemrm=/bin/rm
if [[ -z $1 || $1 == "--help" ]]
then
exec $systemrm
fi
if [[ ! -d $backupdir ]]
then
mkdir -m 0700 $backupdir || echo "$0: Cannot create $backupdir"; exit
fi
args$=$( getopt dfiPRrvw $* ) || exec $systemrm
count=0
flags = ""
foreach argument in $args
do
case $argument in
--) break;
;;
*) flags="$flags $argument";
(( count=$count + 1 ));
;;
esac
done
shift $(( $count ))
for file
do
[[ -e $file ]] || continue
copyfile=$backupdir/$(basename $file).$(date "+%m.%d.%y.%H.%M.%S")
/bin/cp -R $file $copyfile
done
exec $systemrm $=flags "$@"
您应该发现Shell脚本可读,尽管以前没有讨论过一些新东西。 让我们介绍一下这些内容,然后查看整个脚本。
- 当shell启动命令(例如
cp
或ls
,它将为该命令生成一个新进程,然后等待(sub)进程完成后再继续。 该exec
命令也可以启动一个命令,但不是产生一个新的进程,exec
“取代”当前进程的任务-那就是,外壳(或脚本)过程-以新的命令。 换句话说,exec
重用相同的过程来启动新任务。 在脚本的上下文中,exec
立即“终止”脚本并启动指定的任务。 - UNIX实用程序
getopt
扫描位置参数以查找您指定的命名参数。 在这里,dfiPRrvw
列表查找-d
,-f
,-i
,-P
,-R
,-r
,-v
和-w
。 如果出现另一个选项,则getopt
失败。 否则,getopt
返回以特殊字符串--
结尾的选项字符串。 -
shift
命令从左到右删除位置参数。 例如,如果命令行是myrm, -r -f -P file1 file2 file3
,则shift 3
将分别删除$0
,$1
和$2
或-r
,-f
和-P
。file1
,file2
和file3
被重新编号为新的$0
,$1
和$2
。 -
case
语句的工作方式与传统编程语言中的类似:将其参数与列表中的每个模式进行比较; 找到匹配项时,将执行相应的代码。 就像在shell中一样,*
匹配任何内容,并且如果找不到其他匹配项,则可以用作默认操作。 - 标记
$@
扩展为所有(其余)位置参数。 -
zsh
运算符$=
在空白边界处分割单词。$=
当您有一个长字符串并且想要将字符串拆分为单独的参数时很有用。 例如,如果变量x
包含字符串'-r -f'
这是一个包含五个字符的单词-$=x
成为两个单独的单词-r
和-f
。
有了这些说明,您现在应该可以完全剖析脚本了。 让我们看一下代码块:
- 第一个块设置在整个脚本中使用的变量。
- 下一个块应该看起来很熟悉:如果未提供任何参数,它将打印用法消息。 为什么
exec
真正的rm实用程序? 如果将此脚本命名为“ rm”并将其放在$PATH
较早位置,则它可以充当/ bin / rm的代理。 脚本的错误选项还是/ bin / rm的错误选项,因此脚本使/ bin / rm提供使用情况消息。 - 下一个块将创建备份目录(如果不存在)。 如果
mkdir
失败,则脚本会死于相应的错误消息。 - 下一个块在位置参数列表中找到
dash
参数。 如果getopt
成功,则$args
具有选项列表。 如果getopt
失败(在无法识别选项时发生),它将打印一条错误消息,并且脚本会退出并显示用法消息。 - 以下块捕获了字符串中用于rm的所有选项。 当遇到特殊的
getopt
选项--
,累积停止。shift
从参数列表中删除所有已处理的参数,保留要处理的文件和目录的列表。 -
for file
开头的块是复制每个文件或目录的位置,以保存在您的个人“坟墓”中。 每个文件的目录都按原样(-R
)复制到该坟墓中,并且在文件后缀当前日期和时间,以确保该副本是唯一的,并且不会破坏具有相同名称的先前存档条目。 - 最后,使用传递给脚本的相同命令行选项删除文件或目录。
但是,如果您碰巧需要刚删除的文件或目录(偶然吗?),则可以在存档中查找原始副本!
继续自动化
使用UNIX的次数越多,创建脚本的可能性就越大。 脚本节省了重新键入复杂而冗长的命令序列所需的时间和精力,也防止了错误。 Web上充满了其他人为许多目的创建的有用脚本。 很快,您还将发布自己的咒语。
翻译自: https://www.ibm.com/developerworks/aix/library/au-speakingunix6.html