关于本系列
典型的UNIX®管理员经常使用一系列重要的实用程序,技巧和系统,以协助管理过程。 有一些关键实用程序,命令行链和脚本可用来简化不同的过程。 这些工具中的一部分随操作系统一起提供,但是大多数技巧来自多年的经验以及减轻系统管理员生活的渴望。 本系列的重点是从各种不同UNIX环境中的可用工具中获取最大收益,包括简化异构环境中管理的方法。
差异和问题
如果您使用各种UNIX主机,特别是如果每个主机都支持不同的UNIX风格(Berkeley软件发行版(BSD),UNIX系统版本4(VSVR4)以及更高版本)或版本,那么您可能会发现您花了您需要花费大量时间检查并确定主机所在的主机,以便您可以考虑系统运行方式的变化。
例如, ps
命令需要在BSD和基于SVR4的UNIX主机之间使用不同的命令行选项,以整体上获得相同的信息(有关更多详细信息,请参阅系统管理工具包:进程管理技巧 )。 平台之间也存在更广泛的差异。 有时,已更改命令的名称。 Linux®提供adduser
命令,而Solaris提供useradd
命令。
在标准化方面,您可以使用多种途径:
- 您可以选择在您的主平台(例如Solaris)上进行标准化,并在其他平台上的等效命令周围提供包装,以匹配Solaris标准。
- 您可以选择对混合使用的命令进行标准化,这些命令可以为您使用的任务提供最佳的组合,选择并选择所需的命令,并为特定平台上不存在的命令构建包装。
- 您可以创建自己的脚本套件来执行特定任务,包括自己替换
ls
,ps
等通用工具,以便它们生成所需的信息。 这仅是危险的,因为这意味着您可能永远不会使用原始文档,如果脚本不可用,可能会导致潜在的问题。
如何实际实现各种命令周围的包装器以提供兼容的或唯一的层,取决于您是否试图为相同的替代命令简单地提供一个可识别的名称,还是需要围绕一个或多个构建包装器。更多命令以达到等效结果。 有三种潜在的解决方案:
- 别名-仅在某些Shell中支持此解决方案-别名提供了一种将给定字符串扩展为特定命令的简单方法。
- Shell函数-大多数现代Shell支持此解决方案-Shell函数使您可以创建更复杂的序列,但是由于它们是内置函数,因此当差异相对较小时,它们会更加实用。
- Shell脚本-当您构建的包装程序特别复杂时,更好的解决方案是使用可以调用的Shell脚本代替原始命令。 使用Shell脚本,您可以通过替换获得更多创意,甚至可以提供完全由Shell脚本驱动的替代命令。
让我们看一下每个潜在的解决方案以及可以通过此方法模拟的一些示例命令。
使用别名
要为命令设置特定选项时,Korn(ksh),Bourne-Again SHell(bash),TENEX C shell(tcsh)和Z shell(zsh)shell支持的别名提供了最简单的方法,同时仍支持其他选项。 别名恰如其名,可以为一个命令命名另一个别名,也可以为同一命令提供别名并提供其他选项。 别名将从您键入的内容扩展到其扩展名。
例如,通用别名是ll
,它调用ls -l
的等效项( ll
通常称为长列表 )。 每当用户键入ll
,它将直接用扩展替换,因此: $ ll a*
在执行前扩展为: $ ls -la*
。
命令行选项也继续有效,换句话说, $ ll -a
扩展为: $ ls -l -a
。
您还可以为现有命令添加别名。 我将-F
选项添加到所有ls
命令,例如: $ ls
扩展为: $ ls -F
。
要设置别名,请使用内置的shell别名语句,并在引号中指定所需的扩展名。 例如,要设置前面详述的ll
扩展,请使用: $ alias ll='ls -l'
。
别名在您要使用base
命令并轻松指定其他选项的情况下最有用,同时仍允许设置特定于平台的选项。
一个很好的例子是ps
命令,该命令在基于SVR4和基于BSD的UNIX主机上有所不同。 在本系列的第一篇文章中,请参阅系统管理工具包:流程管理技巧 -本文介绍了如何使用ps
的选项来获得类似的清单。 您可以将其与别名一起使用,而不会影响指定其他选项的能力。 例如,在BSD上,您将指定一个类似清单1的别名。
清单1.在BSD上指定一个别名
$ alias ps='ps -o pid,ppid,command'
在SVR4主机上,您将创建清单2之类的别名。
清单2.在SVR4上指定一个别名
$ alias ps='ps -opid,ppid,cmd
现在,在两个系统相对于ps
操作方式的限制内,您从ps
获得了标准输出。 和以前一样,您可以继续添加其他选项; 例如,在添加了-A
选项的情况下,请求在任一平台上安装了别名的所有进程。 它会在BSD(在本例中为Mac OS X)上产生类似于清单3的内容。
清单3.在BSD上使用-A
选项
$ ps -A
PID PPID COMMAND
1 0 /sbin/launchd
23 1 /sbin/dynamic_pager -F /private/var/vm/swapfile
27 1 kextd
32 1 /usr/sbin/KernelEventAgent
33 1 /usr/sbin/mDNSResponder -launchdaemon
34 1 /usr/sbin/netinfod -s local
35 1 /usr/sbin/syslogd
36 1 /usr/sbin/cron
37 1 /usr/sbin/configd
38 1 /usr/sbin/coreaudiod
39 1 /usr/sbin/diskarbitrationd
...
SVR4系统(Gentoo Linux主机)显示相同的列,如清单4所示。
清单4.在SVR4上使用-A
选项
$ ps -A
PID PPID CMD
1 0 init [3]
2 1 [migration/0]
3 1 [ksoftirqd/0]
4 1 [watchdog/0]
5 1 [migration/1]
6 1 [ksoftirqd/1]
7 1 [watchdog/1]
8 1 [events/0]
9 1 [events/1]
10 1 [khelper]
11 1 [kthread]
14 11 [kblockd/0]
15 11 [kblockd/1]
16 11 [kacpid]
...
另一种选择或多或少地反映了本文其他地方给出的脚本和功能解决方案的另一种选择是为给定命令的特定输出创建别名,这些别名采用相同的方法提供相同的格式化输出。 与ps
示例相同,您可以创建别名ps-all
以输出所有进程的列表,并根据需要为每个平台设置适当的扩展。
设置这些别名的最佳位置是在登录期间执行的Shell初始化脚本中,例如.ksh,.profile或.bashrc。 您可以在此处执行相同的系统检查,以验证要启用的别名。 如果要提供一个适用于所有用户的全局解决方案,则将别名定义放入一个公共可用的文件中,例如,在/ etc或/ usr / local中,并设置用户初始化脚本以获取别名定义。
尽管您可以使用别名系统将给定命令扩展为一组多个命令或管道,但别名系统最适合您要在单个命令上设置命令行选项的情况。 这破坏了为扩展中的最后一个命令以外的任何命令指定其他参数的能力。 为了处理这种包装器,外壳中的内联函数可能是更好的选择。
使用内联外壳函数
大多数Shell支持功能,这些功能基本上是微型脚本,您可以在其中放置命令和其他Shell脚本化元素来执行特定任务。 由于它们是主shell定义中的函数,因此它们快速简便地使用,同时仍支持许多与成熟的shell脚本相同的功能,例如命令行参数。
对命令行参数的支持是支持某些无法使用别名的命令和组合的关键。 例如, killall
命令在最基本的级别上将杀死与特定字符串匹配的所有命令。 它并非在所有平台上都可用,但是一旦找到它,您将想在其他环境中使用它。
在Solaris上, killall
命令存在,但它用作关闭进程的一部分以杀死所有进程。 想象一下,意外地在Solaris主机上调用killall
命令来关闭所有Apache进程,却发现您已经有效地关闭了系统!
提供替代品(在所有主机上使用相同的名称或使用不同的名称),可以实现按其名称杀死进程的预期结果,并消除了不必要的错误和潜在的代价高昂的错误,同时扩展了执行此操作的系统的功能。当然不支持该选项。
该命令的关键组件是识别正在运行的进程,提取与给定字符串匹配的进程以及使用kill
命令将KILL
信号发送到每个匹配进程的能力。 在命令行上,您可以通过一系列管道(使用KILL
信号)实现等效,如清单5所示。
清单5.提供替代killall命令
$ ps -ef|grep gcc|awk '{ print $2; }'|xargs kill -9
该命令的关键元素是提供给grep的字符串(在本例中为gcc)以及ps
输出中包含所需进程ID的列。 上面的示例对Solaris主机和大多数SVR4 UNIX变体有效。
在该示例中,别名将不起作用,因为您希望能够在命令中插入的信息不是结尾。 别名通过扩展方法工作。 但是,内联外壳函数是完美的。
在支持Bourne语法的shell(bash和zsh)中,可以使用清单6所示的以下语法定义函数。
清单6.定义一个函数
function NAME()
{
# do stuff here
}
仅在典型的shell脚本中,函数调用时的参数就可以用$1
, $2
等来获得。 因此,您可以定义一个函数,该函数执行与killall
相同的基于字符串的信令(请参见清单7 )。
清单7.定义一个执行与killall相同的信号发送功能
function killall()
{
ps -ef|grep $1|awk '{ print $2; }'|xargs kill -9
}
请注意,函数的awk部分中的$2
不会扩展,因为awk脚本定义使用了单引号,这会阻止扩展,并且在这种情况下,请选择第二列。
与别名一样,指定外壳函数的最佳位置是外壳的初始化脚本中。 功能的局限性在于它们依赖于壳内可用的支持能力,而这种能力并不总是可能的或可用的。
尽管可以随意设置内联Shell函数,但在很多情况下,Shell函数都不理想。 例如,在很长的序列中,您正在模拟一个更复杂的命令,或者在您需要解析命令并提供本地化等效项的命令周围提供包装器,那么内联函数就没什么用处了。 在这里,shell脚本可能更合适。
使用脚本
建立一致环境的最简单,最兼容的方法是创建可以用作真实命令包装程序的Shell脚本,这些脚本考虑了您要支持的各种选项和设置。
例如,在设置参数(例如用户ID或组成员身份)时, useradd
和adduser
命令支持相同的单字母命令行选项,因此Linux: $ adduser -u 1000 -G sales,marketing mcbrown
等同于Solaris : $ useradd -u 1000 -G sales,marketing mcbrown
。
但是,Linux版本还支持扩展的命令选项,例如--uid
和--groups
等效于上述命令行选项。 Solaris不支持这些功能,但是,如果使用名称adduser
创建外壳程序脚本,则可以模拟Linux版本,然后使用适当的选项运行真正的Solaris useradd
命令。
清单8是一个示例shell脚本,充当adduser
或useradd
命令的包装。
清单8.充当包装器的示例shell脚本
#!/bin/bash
# -*- shell-script -*-
for i in $*
do
case $i in
--uid|-u) OPT_UID=$2; shift 2;;
--groups|-G) OPT_GROUPS=$2; shift 2;;
--gid|-g) OPT_GROUP=$2; shift 2;;
--home-dir|-d) OPT_HOMEDIR=$2; shift 2;;
--shell|-s) OPT_SHELL=$2;shift 2;;
--non-unique|-o) OPT_NONUNIQUE=1;shift 2;;
--comment|-c) OPT_COMMENT=$2;shift 2;;
esac
done
OPTS=""
if [ -n "$OPT_$HOMEDIR" ]
then
OPTS="$OPTS -d $OPT_HOMEDIR"
fi
if [ -n "$GROUP" ]
then
OPTS="$OPTS -g $OPT_GROUP"
fi
if [ -n "$OPT_GROUPS" ]
then
OPTS="$OPTS -G $OPT_GROUPS"
fi
if [ -n "$OPT_SHELL" ]
then
OPTS="$OPTS -s $OPT_SHELL"
fi
if [ -n "$OPT_UID" ]
then
OPTS="$OPTS -u $OPT_UID"
fi
if [ -n "$OPT_COMMENT" ]
then
OPTS="$OPTS -c \"$OPT_COMMENT\""
fi
if [ -n "$OPT_NOUNIQUE" ]
then
OPTS="$OPTS -o"
fi
CMD=adduser
UNAME=`uname`
case $UNAME in
Solaris) CMD=useradd;break;;
esac
$CMD $OPTS $*
脚本的关键是foreach
循环,该循环通过提供的命令行参数起作用(并在$*
可用)。 然后,对于每个选项, case
语句尝试使用短格式或长格式并设置变量来标识选项。 命令行开关为$1
。 如果该选项通常后跟一个值(例如,用户ID),则可以将其作为$2
来访问,您可以使用该值将值分配给变量。
确定选项后, shift
语句将从$*
变量列表中删除一个(或多个,如果指定了数字),以便已被识别的命令行参数不再位于下一个$*
变量中循环的迭代。
识别并提取潜在参数后,您要做的就是构建新选项以提供给最终将使用的命令。 由于useradd / adduser都支持参数的缩写形式,因此可以在此基础上构建新的命令选项字符串。 这是通过检查是否已设置相应的变量并将选项添加到命令行来实现的。 请注意使用双引号,以确保原始引号中的引数被保留并正确标识。
现在安装在支持任一原始命令的平台上,您现在可以添加一个用户,以指定所需的选项,包括混合和匹配参数(请参见清单9 )。
清单9.添加一个用户
$ adduser.sh --homedir /etc -g wheel --shell /bin/bash -c "New user" mcbrown
可以使用相同的基本原理来围绕其他命令构建包装器,甚至更改参数名称和选项,或提供等效的表达式。
如果要使用原始名称(例如, adduser
安装此文件并将其放置在目录中(例如,/ usr / local / compat),则必须确保该目录出现在目录中的实际命令目录之前。 PATH
。 如果将兼容性脚本放在/ usr / local / compat目录中,请参见以下示例: $ PATH=/usr/local/compat:$PATH
。
使用单一来源
无论您是使用多个脚本还是单个配置脚本/别名来支持您的统一环境,您都可能希望使用单个脚本套件来支持您的系统。 因此,设置一个新系统以使用标准化脚本(无论它们是独立的脚本还是设置Shell函数和别名),就像将它们复制到新系统一样简单。
您可以通过组合使用命令行工具和Shell流控制(例如if
或case
)来使用单个来源,以选择要使用的各种选项。 这里有两种有用的工具:一种用于标识主机(例如主机名或uname),另一种用于标识平台(uname)。
uname的默认输出是基本操作系统名称,例如Linux或Solaris。 例如,可以将它与case
语句结合使用,以选择正确的别名,如上一节中的ps
示例所示 ,如清单10所示 。
清单10. uname的输出
UNAME='uname'
case "$UNAME" in
FreeBSD|NetBSD|Darwin)
alias ps='ps -o pid,ppid,command'
break
;;
Solaris|Linux)
alias ps='ps -o pid,ppid,cmd'
break
;;
esac
脚本中可以使用相同的基本过程来选择特定的序列。
使用内联外壳函数时,通常使用这样的包装器来选择正确的函数定义要容易得多,而不是每次使用函数时都要做出决定,因为这样做会更有效。
摘要
规范环境对简化管理大有帮助。 它减轻了您在使用哪个系统以及哪个命令和/或选项集最适合获取所需信息或执行所需操作时的负担。 为每个命令选择正确的系统完全取决于命令和您要实现的目标。
在要调用命令行选项的单个命令上,最好使用别名系统。 内联函数最适合要轻松嵌入到当前脚本环境中的更复杂的操作和序列,而成熟的单个脚本最适合复杂的多步操作或要为命令提供支持的位置或选项,而无需更改外壳环境。
尽管有明显的优势,但重要的是要记住,如果发生故障并且您无权访问脚本,那么过多地将自己与基础系统隔离可能会使您处于未准备好的状态-您应该寻求扩展和扩充,而不是替换。
翻译自: https://www.ibm.com/developerworks/aix/library/au-satstandardsh.html