Shell脚本学习指南(七)——产生脚本

news2024/11/27 10:33:31

文章目录

  • 前言
  • 路径查找
  • 软件构建自动化


前言

本篇,我们将进一步处理更复杂的工作。我们认为这里举出的例子都是一般用得到的工具,它们每一个都截然不同,且在大多数UNIX工具集里也没有。

在篇中的程序,包括命令行参数分析、在远程主机上运算、环境变量、工作记录、并行处理、使用eval的运行时语句执行、草稿文件、Shell函数、用户定义初始文件,以及安全性议题考虑的范例。程序会运用Shell语言里的重要语句,并展现传统的UNIX Shell脚本编写风格。

路径查找

有些程序支持在目录路径上查找输入文件,有点像UNIX Shell查找以冒号隔开的目录列表,列在PATH内,以找出可执行的程序。这对用户来说很方便,他只要提供较短的文件名,且不需要知道它们在文件系统里的位置。UNIX并为提及任何特殊命令或系统调用,可在查找路径下寻找文件,即使在很久以前有其他操作系统支持这种功能。幸好,要作路径查找不难,只要用对工具就行。

与其实现路径查找以寻找特定程序,不如做一个新的工具,以变量遍历名称为参数,而环境变量名称展开是预期的查找路径,后面接着零个或更多的文件模式,并报告匹配文件的位置。我们的程序将在其他需要路径查找支持的软件中,成为一般性工具(这也就是我们先前提到的“构建特定工具前,先想象”的原则)。

有时你得知道路径下的某个文件是不是不止一个,因为当路径下存有不同的版本时,你可能需要调整路径,控制要找到的版本。我们的程序会为用户提供一个命令行选项,以选择报告第一个找到的文件还是报告所有找到的文件。另外,根据用户要求提供一个可识别的版本编号,变成软件的标准实现,而且必须提供简短的在线帮助,让用户不需要在每次使用程序时,都重读程序的使用手册才能想起选项的名称。我们的程序当然也提供这样的功能。

我们先分析伪码程序,视为了注释与描述Shell程序代码的各个片段的顺序,以便说明。

我们从一般性的介绍性注释块开始,首行为识别程序的神奇行,/bin/sh来执行脚本,注释块后,接的是程序行为的描述,并说明使用方法:

#! /bin/sh -
# 
# 在查找路径下寻找一个或多个原始文件或文件模式,
# 查找路径由一个指定的环境变量所定义
# 
# 标准输出产生的结果,通常是在查找路径下找到的每个文件之第一个实体的完整路径,
# 或是“filename:not found”的标准错误输出。
# 
# 如果所有文件都找到,则退出码为0
# 否则,即为找不到的文件个数 - 非零值
# (Shell 的退出码限制为 125)。
#
#
# 语法:
#     pathfind [--all] [--?] [--help] [--version] envar pattern(s)
#
# 选项 --all指的是寻找路径下的所有目录
# 而不是找到第一个就停止

在网络的环境下,安全性一直是必须慎重考虑的问题。其中有一种攻击Shell脚本的方式,是利用输入字段分隔字符:IFS,它会影响Shell接下来对输入数据解释的方式。为避免此类型的攻击,部分Shell仅在脚本执行前,将IFS重设为标准值;其他则导入该变量的一个外部设置。我们则是将自己做的预防操作放在脚本的第一步:

IFS='

很难在屏幕上或是显示的页面上看出位于引号里的内容:它们是具有三个字符的字符串,包括换行字符、空格、以及定位字符(tab)。IFS的默认值为:空格,定位字符、换行符,不过如果我们以这种方式编写,那些会自动修建空白的编辑器可能会将结尾的空白截去,让字符串的值减少成只有一个换行字符。比较好的方式应该是更严谨的使用转义字符,例如IFS="\040\t\n",可是Bourne Shell并不支持这样的转义字符。

在我们重新定义IFS时有一点请特别留意。当"$*"展开以恢复命令行时,IFS值的第一个字符,会被当成字段分隔符。我们在这个脚本里不使用"$*",所以重新安排IFS内的字符不会有影响。

另一种常见的安全性攻击,则是欺骗软件,它执行非我们所预期的命令。为了阻断这种攻击,我们希望调用的程序是可信任的版本,而非潜伏在用户提供的查找路径下的欺骗程序,因此我们将PATH重设为一个最小值,以存储初始值供以后使用:

OLDPATH="$PATH"
PATH=/bin:/usr/bin
export PATH

export语句是这里的关键:它可以确保所有子进程继承我们的安全查找路径。

程序代码接下来是5个以字母顺序排列的简短函数。

第一个函数为error()函数,在标准错误输出上显示其参数,再调用一个函数(此部分稍后说明),不返回:

error()
{
    echo "$@" 1>&2
    usage_and_exit 1
}

第二个函数usage()会写出简短信息,显示程序的使用方式,并返回给它的调用者。需留意的是:这个函数需要程序名称,但不是以直接编码模式,它是从变量PROGRAM取得程序名称,这个变量设置为程序被调用的名称。这可以让安装程序在重新命名程序时,无须修改程序代码,这常发生在与已安装的程序名称产生冲突时,也即具同样名称但具有不同用途的时候。函数本身很简单:

usage()
{
    echo "Usage: $PROGRAM [--all] [--?] [--help] [--version] envvar pattern(s)"
}

第三个函数usage_and_exit(),会产生语法信息,并以它的单一参数所提供的状态码退出:

usage_and_exit()
{
    usage
    exit $1
}

第四个参数version()是在标准输出上显示程序版本编号,并返回给它的调用者。如同usage(),它是使用PROGRAM取得程序名称:

version()
{
    echo "$PROGRAM version $VERSION"
}

最后一个函数warnning()会在标准错误上显示它的参数,并对变量EXITCODE加1,可记录已发出的警告信息的数目,并返回给调用者:

warnning()
{
    echo "$@" 1>&2
    EXITCODE=`expr $$EXITCODE +1`
}

EXITCODE的自增运算POSIX中可以使用EXITCODE=$((EXITCODE + 1)),不过还是有相当多系统不认得该POSIX的这个用法。

即使程序很短,我们其实完全不用写函数,除了避免程序代码重复外,其实它可以隐藏不相关的细节,这是良好的程序实现方式:告知我们正要做什么,而不是说明我们要如何作。

这时我们已经到达运行时的第一个被执行的语句了。先初始化5个变量,以记录选项的选择、用户提供的环境变量名称、退出码、程序名称以及程序版本编号:

all=no
envvar=
EXITCODE=0
PROGRAM=`basename $0`
VERSION=1.0

在我们的程序里,将遵循小写(字母)变量为本地函数或主程序代码体所使用,而大写变量则被整个程序全局性地共享。这里给all变量一个字符串值,而非一个数字,是因为这样可以让程序更清楚,而对运行时资源消耗的影响也微乎其微。

接下来是大型代码块,是在所有的UNIX程序里典型的命令行参数解析:当我们有一个参数时(由参数计数值$#决定,且必须大于零),会根据参数的字符串值来选择case语句的程序块,处理该函数:

while test $# -gt 0
do
    case $1 in

case选择器在不同环境下可能有不同的解读方式。GNU程序风格鼓励使用长的、描述性的选项名称,而不是长久以来用于UNIX里的那套旧的、隐秘式的单一字符选项。后者简洁式的做法,在选项数很少且程序使用频繁的时候还让人可以接受,如果不是这种情况,描述性的名称会比较好,用户只需要提供足够的信息——不要和其他选项重复即可。然而,当有其他程序提供相同的选项时,则应避免这种简略用法,这么做才能让用户更容易了解程序,确保日后程序新版本加入新选项时,不会产生意外的结果。

针对--all选项,我们只要通过把变量all重新设置为yes来记录找到选项的事实即可:

--all | --al |--a | -all | -al| -a)
    all=yes
    ;;

--version、--help同理

case选择器-*)会匹配剩下的所有选项:我们会在标准错误输出上报告非法选项,并调用usage()函数,提醒用户用法为何,再立即以失败状态码(1)退出:

-*)
    error "Unrecongnized option: $1"
    ;;

这里,匹配指出我们已经处理完所有的选项,所有可以退出循环。由于我们已处理完所有的情况,所以这里用终结关键字来结束case语句:

*)
    break
    ;;
esac

程序pathfind的完整内容

#! /bin/sh -
# 
# 在查找路径下寻找一个或多个原始文件或文件模式,
# 查找路径由一个指定的环境变量所定义
# 
# 标准输出产生的结果,通常是在查找路径下找到的每个文件之第一个实体的完整路径,
# 或是“filename:not found”的标准错误输出。
# 
# 如果所有文件都找到,则退出码为0
# 否则,即为找不到的文件个数 - 非零值
# (Shell 的退出码限制为 125)。
#
#
# 语法:
#     pathfind [--all] [--?] [--help] [--version] envar pattern(s)
#
# 选项 --all指的是寻找路径下的所有目录
# 而不是找到第一个就停止
IFS=' '

OLDPATH="$PATH"

PATH=/bin:/usr/bin
export PATH


error()
{
    echo "$@" 1>&2
    usage_and_exit 1
}

usage()
{
    echo "Usage: $PROGRAM [--all] [--?] [--help] [--veriosn] envvar pattern(s)"
}

usage_and_exit()
{
    usage
    exit $1
}


version()
{
    echo "$PROGRAM version $VERSION"
}


warning()
{
    echo "$@" 1>&2
    EXITCODE=`expr $EXITCODE + 1`
}

all=no
envvar=
EXITCODE=0
PROGRAM=`basename $0`
VERSION=1.0

while test $# -gt 0
do
    case $1 in 
    --all | --al | --a | -all | -al | -a ) all==yes ;;
    --help | --hel | --he | --h | '--?' | -help | -hel | -he | -h | '-?' )
        usage_and_exit 0
        ;;
    --version | --versio | --versi | --vers | --ver | --ve | --v | \
    -version | -versio | -versi | -vers | -ver | -ve | -v )
        version
        exit 0
        ;;
    -*)
        error "Unrecognized option: $1"
        ;;
    *)
        break
        ;;
    esac
    shift
done


envvar="$1"
test $# -gt 0 && shift
test "x$envvar" = "xPATH" && envvar=OLDPATH


dirpath=`eval echo '${'"$envvar"'}' 2>/dev/null | tr : ' '`
# 为错误情况进行健全检测
if test -z "$envvar"
then
    error Environment variable missing or empty
elif test "x$dirpath" = "x$envvar"
then
    error "Broken sh on this platform: cannot expand $envvar"
elif test -z "$dirpath"
then
    error Empty directory search path
elif test $# -eq 0
then
    exit 0
fi

for pattern in "$@"
do
    result=
    for dir in $dirpath
    do
        for file in $dir/$pattern
        do
            if test -f "$file"
            then
                result="$file"
                echo $result
                test "$all" = "no" && break 2  
            fi
        done
    done
    test -z  "$result" && warning "$pattern: not found"
done
# 限制退出状态是一般UNIX实现上的限制
test $EXITCODE -gt 125 && EXITCODE=125

exit $EXITCODE

注意:break 2指的是跳出这里的2层循环

测试案例:
在这里插入图片描述
在这里插入图片描述

pathfind确实是一个很有用的练习。除了它是个方便的新工具程序,而标准的GNU、POSIX以及UNIX工具集里都没有之外,它还拥有所有大多数UNIX程序的主要组成部分:参数解析、选项处理、错误报告以及数据处理。我们还说明了消除几个著名安全漏洞的三个步骤:加入-选项已终止起始的Shell命令行,立即设置IFS及PATH。该程序代码的好处是可以再利用,只需要作一点点修改,例如:前置的注释标志,IFS与PATH的分配、5个辅助函数、处理参数的while与case语句,而且至少外部循环会遍历收集命令行上的文件。

作为一个练习,你可以开始考虑,是不是该为pathfind的这些扩展做出一些改变:

  • 将标准输出与标准错误输出的重定向存储到/dev/null,并加上--quiet选项抑制所有输出,所以唯一会看到的便是指出是否找到匹配的退出码。这个好用的程序功能,在cmp-sgrep-q选项里已经有了。

  • 加上--trace选项,将每个要测试的文件完整路径响应到标准错误输出。

  • 加上--test x选项,让test的-f选项可以置换为其他值,例如-h(文件为符号性连接)、-r(文件是可读取的)、-x(文件是可具执行的)等。

  • pathfind扮演过滤器功能:如果命令行上没有指定的文件名,则它应该自标准输入读取文件列表。这么做会对程序的架构与组织产生什么样的影响?

  • 修补所有你能找得到的安全漏洞,例如最新安全性公告所列的议题。

有兴趣的同学,自己研究...哈哈哈

软件构建自动化

由于UNIX可运行在多种平台,因此在构建软件包时,常见的实现方式是从源代码开始安装,而非直接安装二进制包。大型的UNIX站点常由数个平台结合而成,对管理者而言,最冗长麻烦的工作就是将包安装在这些不同的系统上。而这正是自动化的好机会。很多软件开发人员,已直接采用GNU项目下所开发的软件包惯例,包括:

  • 包以压缩存档文件package-x.y.z.tar,gz(或package-x.y.z.tar.bz2)的形式发布,文件解开后将出现在package-x.y.z目录下。

  • 顶层configure脚本通常是由GNU的autoconf命令,通过configure.in或configure.ac文件里的规则列表自动产生。执行该脚本时,有时得加上一些命令行选项,便能产生定制的C/C++头文件,通常叫做config.h、衍生自Makefile.in(模板文件)的一个定制Makefile,并且有时候还会有其他文件。

  • Makefile目标(target)的标准集已详述于《The GNU Coding Standards》中,有all(全部构建)、check(执行验证测试)、clean(删除不需要的中间文件)、distclean(恢复目录到它的原始发布),以及install(在本地系统上安装所有必需的文件)。

  • 被安装的文件位于Makefile文件里prefix变量所定义的默认树状结构目录下,并且可在配置时使用--prefix=dir命令行选项进行设置,或是通过一个本地系统范围的定制文件提供。默认的prefix为/usr/local,但无权限的用户可能得使用$HOME/local,或是使用$HOME/``arch/local更好。其中arch是一条命令,它会显示定义平台的简短说明。GNU/Linux与Sun Solaris提供的是/bin/arch。在其他平台下,安装自己的实作程序时,通常只是使用简单的Shell脚本包装(wrapper),在搭配适当的echo命令。

接下来的工作就是建立脚本,它被给定一个包列表,在目前系统下的许多标准位置之一,找到它们的源分发,将它们复制到远程主机列表中的每一个,在哪里解开它们,然后编译并使其成为合法可用状态。我们发现自动化安装步骤不是聪明的做法:构建日志必须先审慎地检查。

这个脚本必须让UNIX站点里的所有用户都能使用,所以我们不能在它里面内嵌有关特定主机的信息。我们假设用户已经提供了两个定制文件:directories——列出要查找包分发文件的位置,以及userhosts——列出用户名称、远程主机名称、远程构建目录以及特殊环境变量。我们将这些及其他相关文件放在隐藏目录$HOME/.build下,以降低混乱的程度。然而,因为在同一个站点内所有用户下的来源目录列表可能都相类似,所以我们包括一个合理的默认列表,就不再需要directories文件。

有时候构建要能够只在一般构建主机的子集主机上完成,或是使用不再一般位置里的存档(archive)文件,因此,脚本要能够在命令行上设置这些值。

我们在这里开发的脚本可以这样调用:

$ build-all coreutils-5.2.1 gawk-3.1.4  #在所有主机上构建这两个包
$ build-all --on loaner.example.com gnupg-1.2.4  #在指定主机上构建包
$ build-all --source $HOME/work butter-0.3.7  #从非标准位置中构建包

这些命令其实做了很多事,下面我们大致列出,处理每个指定的软件包及在默认的或选定的构建主机上安装的步骤:

  1. 在本地文件系统下寻找包分发文件。

  2. 将分发文件复制到远程构建主机。

  3. 初始化远程主机上的登录连接。

  4. 切换到远程构建目录,并解开分发文件。

  5. 切换到包构建目录并设置、构建于测试包。

  6. 将初始化主机上的所有输出,分别为每个包与构建环境,记录在分开的日志文件中。

在远程主机上的构建以并行方式进行,所以安装执行所需要的总时间是以最慢的那台机器为准,而不是把所有单个时间求和。对于动辄安装百种以上不同环境系统的我们来说,幸好有build-all程序,这也为包开发人员提供了一个不错的挑战。

完整程序如下:

#! /bin/sh -
# 在一台或多台构建主机上,并行构建一个或多个包
#
# 语法:
#       build-all [ --? ]
#                 [ --all "..." ]
#                 [ --check "..." ]
#                 [ --configure "..." ]
#                 [ --environment "..." ]
#                 [ --help ]
#                 [ --logdirectory dir ]
#                 [ --on "[user@]host[:dir][,envfile] ..." ]
#                 [ --source "dir ..." ]
#                 [ --userhosts "file(s)" ]
#                 [ --version ]
#                 package(s)
#
# 可选用的初始化文件:
#       $HOME/.build/directories    list of source directories
#       $HOME/.build/userhosts      list of [user@]host[:dir][,envfile]
IFS=' '

PATH=/usr/local/bin:/bin:/usr/bin
export PATH

UMASK=002
umask $UMASK

build_one()
{
    #语法:
    #   build_one [user@]host[:build-directory][,envfile]
    arg="`eval echo $1`"

    userhost="`echo $arg | sed -e 's/:.*$//'`"
    user="`echo $userhost | sed -e s'/@.*$//'`"
    test "$user" = "$userhost" && user=$USER

    host="`echo $userhost | sed -e s'/^[^@]*@//'`"

    envfile="`echo $arg | sed -e 's/^[^,]*,//'`"
    test "$envfile" = "$arg" && envfile=/dev/null

    builddir="`echo $arg | sed -e s'/^.*://' -e 's/,.*//'`"
    test "$builddir" = "$arg" && builddir=/tmp

    parbase=`basename $PARFILE`

    #NB:如果这些模式被更换过,则更新find_package()
    package="`echo ¥parbase | \
            sed     -e 's/[.]jar$//' \
                    -e 's/[.]tar[.]bz2$//' \
                    -e 's/[.]tar[.]gz$//' \
                    -e 's/[.]tar[.]Z$//' \
                    -e 's/[.]tar$//' \
                    -e 's/[.]tgz$//' \
                    -e 's/[.]zip$//'`" 
    #如果我们在远程主机上看不到包文件,则复制过去
    echo $SSH $SSHFLAGS $userhost "test -f $PARFILE"
    if $SSH $SSHFLAGS $userhost "test -f $PARFILE"
    then
        parbaselocal=$PARFILE
    else
        parbaselocal=$parbase
        echo $SCP $PARFILE $userhost:$builddir
        $SCP $PARFILE $userhost:$builddir
    fi

    # 在远程主机上解开存档文件、构建,
    # 及以后台执行方式检查它
    sleep 1  #为了保证唯一的日志文件名
    now="`date $DATEFLAGS`"
    logfile="$package.$host.$now.log"
    nice $SSH $SSHFLAGS $userhost "
        echo '========================================================';
        test -f $BUILDBEGIN && . $BUILDBEGIN || \
            true;
        echo 'Package:          $package' ;
        echo 'Archive:          $PARFILE' ;
        echo 'Date:             $now' ;
        echo 'Local user:       $USER' ;
        echo 'Local host:       `hostname`' ;
        echo 'Local log directory:  $LOGDIR' ;
        echo 'Local log file:       $logfile' ;
        echo 'Remote user:      $user' ;
        echo 'Remote host:      $host' ;
        echo 'Remote directory:     $builddir' ;
        printf 'Remote date:            ' ;
        date $DATEFLAGS ;
        printf 'Remote uname:           ' ;
        uname -a || true ;
        printf 'Remote gcc version:     ' ;
        gcc --version | head -n 1 || echo ;
        printf 'Remote g++ version:     ' ;
        g++ --version | head -n 1 || echo ;
        echo 'Configure environment:    `$STRIPCOMMENTS $envfile | $JOINLINES`' ;
        echo 'Extra environment:    $EXTRAENVIRONMENT' ;
        echo 'Configure directory:  $CONFIGUREDIR' ;
        echo 'Configure flags:      $CONFIGUREFLAGS' ;
        echo 'Make all targets:     $ALLTARGETS' ;
        echo 'Make check targets:   $CHECKTARGETS' ;
        echo 'Disk free report for      $builddir/$package:' ;
        df $builddir | $INDENT
        echo 'Environment:' ;
        env | env LC_ALL=C sort | $INDENT ;
        echo '===========================================================' ;
        umask $UMASK ;
        cd $builddir || exit 1 ;
        /bin/rm -rf $builddir/$package ;
        $PAR $parbaselocal ;
        test "$parbase" = "$parbaselocal" && /bin/rm -f $parbase ;
        cd $package/$CONFIGUREDIR || exit 1 ;
        test -f configure && \
            chmod a+x configure && \
                env `$STRIPCOMMETS $envfile | $JOINLINES` \
                    $EXTRAENVIRONMENT \
                        nice time ./configure $CONFIGUREFLAGS ;
        nice time make $ALLTARGETS && nice time make $CHECKTARGETS ;
        echo '=============================================================' ;
        echo 'Disk free report for $builddir/$package:' ;
        df $builddir | $INDENT ;
        printf 'Remote date:        ' ;
        date $DATEFLAGS ;
        cd ;
        test -f $BUILDEND && . $BUILDEND || \
            test -f $BUILDEND && source $BUILDEND || \
                true;
        echo '===============================================================' ;
    "< /dev/null > "$LOGDIR/$logfile" 2>&1 &
}

error()
{
    echo "$@" 1>&2
    usage_and_exit 1
}

find_file()
{
    # 语法:
    #       find_file file program-and-args
    # 如果找到,返回 0 (成功),如果找不到则返回 1 (失败)
    if test -r "$1"
    then
        PAR="$2"
        PARFILE="$1"
        return 0
    else   
        return 1
    fi
}

find_package()
{
    # 语法:find_package package-x.y.z
    base=`echo "$1" | sed -e 's/[-_][.]*[0-9].*$//'`
    PAR=
    PARFILE=
    for srcdir in $SRCDIRS
    do
        test "srcdir" = "." && srcdir="`pwd`"
        for subdir in "$base" ""
        do
            # NB:如果此列表有改变, 则更新build_one()内的包设置
            find_file $srcdir/$subdir/$1.tar.gz "tar xfz"   && return 
            find_file $srcdir/$subdir/$1.tar.Z  "tar xfz"   && return
            find_file $srcdir/$subdir/$1.tar    "tar xf"    && return
            find_file $srcdir/$subdir/$1.tar.bz2 "tar xfj"  && return
            find_file $srcdir/$subdir/$1.tgz    "tar xfz"   && return
            find_file $srcdir/$subdir/$1.zip    "unzip -q"  && return
            find_file $srcdir/$subdir/$1.jar    "jar xf"    && return
        done
    done
}

set_userhosts()
{
    # 语法:set_userhosts file(s)
    for u in "$@"
    do
        if test -r "$u"
        then
            ALTUSERHOSTS="$ALTUSERHOSTS $u"
        elif test -r "$BUILDHOME/$u"
        then
            ALTUSERHOSTS="$ALTUSERHOSTS $BUILDHOME/$u"
        else
            error  "File not found: $u"
        fi
    done
}

usage()
{
    cat <<EOF
Usage:
        $PROGRAM [ -- ? ]
                 [ --all "..." ]
                 [ --check "..." ]
                 [ --configure "..." ]
                 [ --environment "..." ]                 [ --help ]
                 [ --logdirectory dir ]
                 [ --on "[user@]host[:dir][,envfile] ..." ]
                 [ --source "dir ..." ]
                 [ --userhosts "file(s)" ]
                 [ --version ]
                 package(s)
EOF
}

usage_and_exit()
{
    usage
    exit $1
}

version()
{
    echo "$PROGRAM version $VERSION"
}

warning()
{
    echo "$@" 1>&2
    EXITCODE=`expr $EXITCODE + 1`
}

ALLTARGETS=
altlogdir=
altsrcdirs=
ALTUSERHOSTS=
BUILDBEGIN=./.build/begin
BUILDEND=./.build/end
BUILDHOME=$HOME/.build
CHECKTARGETS=check
CONFIGUREDIR=.
CONFIGUREFLAGS=
DATEFLAGS="+%Y.%m.%d.%H.%M.%S"
EXITCODE=0
EXTRAENVIRONMENT=
INDENT="awk '{ print \"\t\t\t\" \$0 }'"
JOINLINES="tr '\n' '\040'"
LOGDIR=
PROGRAM=`basename $0`
SCP=scp
SSH=ssh
SSHFLAGS=${SSHFLAGS--x}
STRIPCOMMENTS='sed -e s/#.*$//'
userhosts=
VERSION=1.0

# 默认初始化文件
defaultdirectories=$BUILDHOME/directories
defaultuserhosts=$BUILDHOME/userhosts

# 要寻找包分发的位置列表,
# 如果用户未提供个人化列表,则使用默认列表
SRCDIRS="`$STRIPCOMMETS $defaultdirectories 2> /dev/null`"
test -z "$SRCDIRS" && \
    SRCDIRS="
            .
            /usr/local/src
            /usr/local/gnu/src
            $HOME/src
            $HOME/gnu/src
            /tmp
            /usr/tmp
            /var/tmp
    "
while test $# -gt 0
do
    case $1 in 
    --all | --al | --a | -all | -al | -a ) 
        shift
        ALLTARGETS="$1"
        ;;
    --cd | -cd )
        shift
        CONFIGUREDIR="$1"
        ;;
    --check | --chec | --che | --ch  | -check | -chec | -che | -ch )
        shift
        CHECKTARGETS="$1"
        ;;
    --configure | --configur | --configu | --config | --confi | \
    --conf | --con | --co | \
    -configure | -configur | -configu | -config | -confi | \
    -conf | -con | -co ) 
        shift
        CONFIGUREFLAGS="$1"
        ;;
    --environment | --environmen | --environme | --environm | --environ | \
    --enviro | --envir | --envi | --env | --en | --e | \
    -environment | -environmen | -environme | -environm | -environ | \
    -enviro | -envir | -envi | -env | -en | -e )
        shift
        EXTRAENVIRONMENT="$1"
        ;;
    --help | --hel | --he | --h | '--?' | -help | -hel | -he | -h )
        usage_and_exit 0
        ;;
    --logdirectory | --logdirector | --logdirecto | --logdirect | \
    --logdirec | --logdire | --logdir | --logdi | --logd | --log | \
    --lo | --l | \
    -logdirectory | -logdirector | -logdirecto | -logdirect | \
    -logdirec | -logdire | -logdir | -logdi | -logd | -log | -lo | -l )
        shift
        altlogdir="$1"
        ;;
    --on | --o | -on | -o )
        shift
        userhosts="$userhosts $1"
        ;;

    --source | --sourc | --sour | --sou | --so | --s | \
    -source | -sourc | -sour | -sou | -so  | -s )
        shift
        altsrcdirs="$altsrcdirs $1"
        ;;
    --userhosts | --userhost | --userhos | --userho | --userh | \
    --user | --use | --us | --u | \
    -userhosts | -userhost | -userhos | -userho | -userh | \
    -user | -use | -us | -u )
        shift
        set_userhosts $1
        ;;
    --version | --versio | --versio | --versi | --vers | --ver | --ve | --v | \
    -version | -versio | -versi | -vers | -ver | -ve | -v )
        version 
        exit 0
        ;;
    -*)
        error "Unrecognized option: $1"
        ;;
    *)
        break;
        ;;
    esac
    shift
done

# 寻找适当的邮件客户端程序
for MAIL in /bin/mailx /usr/bin/mailx /usr/sbin/mailx /usr/ucb/mailx \
            /bin/mail /usr/bin/mail
do
    test -x $MAIL && break
done
test -x $MAIL || error "Cannot find mail client"

# 命令行来源目录优先于默认值
SRCDIRS="$altsrcdirs $SRCDIRS"

if test -n "$userhosts"
then
    test -n "$ALTUSERHOSTS" && 
        userhosts="$userhosts `$STRIPCOMMENTS $ALTUSERHOSTS 2> /dev/null`"
else
    test -z "$ALTUSERHOSTS" && ALTUSERHOSTS="$defaultuserhosts"
    userhosts="`$STRIPCOMMETS $ALTUSERHOSTS 2> /dev/null`"
fi

#检查是否要执行某些操作
test -z "$userhosts" && usage_and_exit 1

for p in "$@"
do
    find_package "$p"

    if test -z "$PARFILE"
    then
        warning "Cannot find package file $p"
        continue
    fi

    LOGDIR="$altlogdir"
    if test -z "$LOGDIR" -o ! -d "$LOGDIR" -o ! -w "$LOGDIR"
    then
        for LOGDIR in "`dirname $PARFILE`/logs/$p" $BUILDHOME/logs/$p \
                        /usr/tmp /var/tmp /tmp
        do
            test -d "$LOGDIR" || mkdir -p "$LOGDIR" 2> /dev/null
            test -d "$LOGDIR"  -a -w "$LOGDIR" && break
        done
    fi

    msg="Check build logs for $p in `hostname`:$LOGDIR"
    echo "$msg"
    echo "$msg" | $MAIL -s "$msg" $USER 2> /dev/null

    for u in $userhosts
    do
        build_one $u
    done
done

#将退出状态限制为一般 UNIX 实际的做法
test $EXITCODE -gt 125 && EXITCODE=125

exit $EXITCODE

大家自行阅读就好啦,有问题评论区讨论或网上搜索即可!肯定是有点难度的,慢慢来

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/85900.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

电气数据|IEEE118(含风能太阳能)

1 概述 在本科或者研究生写论文时&#xff0c;经常需要用到很多数据&#xff0c;所以数据显得尤为重要。 下面这段文字&#xff0c;为了字数需要&#xff0c;所以可以忽略。 现代这种“探索、征服”的心态&#xff0c;从世界地图的演变可以看得一目了然。早在历史进到现代之前…

猿如意中的【PyCharm社区版】工具详情介绍

猿如意中的【PyCharm社区版】工具详情介绍&#xff0c;手把手教你使用猿如意下载、安装和配置【PyCharm社区版】&#xff0c;希望能帮助到有需要的童鞋。 文章目录前言一、猿如意介绍二、PyCharm社区版开发工具简介1.【PyCharm专业版】和【PyCharm社区版】的功能区别2.【PyChar…

新兴新能源设施[1]--盐穴压缩空气储能相关配套设施

新兴新能源设施[1]--盐穴压缩空气储能相关配套设施前言1. 什么是盐穴压缩空气储能&#xff1f;2. 盐穴储气的相关应用案例2.1 江苏常州应用案例2.2 衡阳压缩空气储能项目2.3 山东肥城10MW盐穴压缩空气储能系统3. 气-液、油-水界面信息探测和信息传输3.1 中子寿命测试油水界面&a…

一种近红外区荧光染料:ICG-Tetrazine,ICG-TZ,在影像医学,光热 等都会有一定的应用

凯新生物一种近红外区荧光染料&#xff0c;Cy系列&#xff08;花菁类&#xff09;染料&#xff08;630-670 nm、650-700 nm&#xff09;&#xff0c;在影像医学&#xff0c;光热 等都会有一定的应用。和四嗪基团进行结合&#xff0c;进行相应的荧光标记。 【英文名称】 ICG-Te…

Linux 的常用命令

前言 本篇博客给大家介绍一些常见的 Linux 命令 目录操作 pwd 查看当前工作目录 clear 清除屏幕 cd ~ 当前用户目录 cd / 根目录 cd - 上一次访问的目录 cd .. 上一级目录 其中清除屏幕的快捷键是: ctrl l ls 语法: ls 选项 目录或文件 功能: 对于目录来说…

VS实现一个爬虫程序<c++>获取网页源代码

目的:写一个爬虫程序,可以进入http开头的网页,将网页的源代码显示出来. 结果展示: 思路: 1.解析网址 2.连接服务器->发请求 3.接收源代码并存储起来 函数: 1.解析网址函数: http://......./...... //....../之间的字符我们将其存储到urlAddr[]当中 /......之后的字符…

CentOS7安装Docker

1. 确定是CentOS8及其以上版本 $cat /etc/redhat-release 2. 卸载旧版本 $sudo yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-engine 3. yum安装gcc相关 $yum -y install g…

自动化部署npm安装sass是报错,往往是node版本太高导致的

本想尝试一下github的自动化部署&#xff0c;将一个vue项目部署到github pages上面&#xff0c;结果报错&#xff1a; 报错&#xff1a; npm ERR! code 1 50npm ERR! path /home/runner/work/CvReport/CvReport/node_modules/node-sass 51npm ERR! command failed 52npm ERR!…

感性认识:计算机基本工作原理

一. 计算机发展史概述 1946年2月14日&#xff0c;在美国宾夕法尼亚大学&#xff0c;众所周知的世界上第一台电子数字计算机ENIAC诞生。然而&#xff0c;英国在二战期间研制的电子计算机Colossus(巨人)却要比ENIAC早两年&#xff0c;多年来&#xff0c;英国人对自己研制首台电脑…

运筹说 第85期 | 只有初中学历的数学家

有学者形容华罗庚是在中国现代数学洪荒之地中抱定“战士死在沙场幸甚”的开拓者。这一句话足以肯定华罗庚教授在我国数学领域重要地位&#xff0c;下面我们就来一起领略伟人的风采。 &#xff08;华罗庚&#xff0c;1910年11月12日-1985年6月12日&#xff09; 华罗庚&#xff0…

预尝试4个小时入门Vue

目录 Vue简介 ​编辑 第一个vue程序 el挂载点 data数据对象 Vue指令 v-text和v-html v-on v-show v-if v-bind v-for v-model 记事本案例 axios网络请求库 1、安装 2、使用&#xff0c;作一个音乐播放器 均来自黑马教程。 Vue简介 第一个vue程序 <!DOCTYP…

用 Python 编写的 Python 解释器,你会吗?

计算机只能理解机器码。归根结底&#xff0c;编程语言只是一串文字&#xff0c;目的是为了让人类更容易编写他们想让计算机做的事情。真正的魔法是由编译器和解释器完成&#xff0c;它们弥合了两者之间的差距。解释器逐行读取代码并将其转换为机器码。 在本文中&#xff0c;我…

java计算机毕业设计基于安卓Android的儿童疫苗接种管理App

项目介绍 本文讲述了儿童疫苗管理App的设计与实现。结合电子管理系统的特点,分析了儿童疫苗管理App的现状,给出了儿童疫苗管理App实现的设计方案。 本论文主要完成不同用户的权限划分,不同用户具有不同权限的操作功能,在客户端,主要有管理员、医生和用户三个角色可以登录,用户…

【react】JSX基本语法

1、全称 JavaScript XML 2、定义 是react定义的一种类似于XML的JS扩展语法: JS XML本质是React.createElement(component, props, …children)方法的语法糖 3、作用: 用来简化创建虚拟DOM 4、标签名任意 HTML标签或其它标签 5、标签属性任意 HTML标签属性或其它 6、基本语法规…

Python 缩进语法的起源:上世纪 60-70 年代的大胆创意!

上个月&#xff0c;Python 之父 Guido van Rossum 在推特上转发了一篇文章《The Origins of Python》&#xff0c;引起了我的强烈兴趣。 众所周知&#xff0c;Guido 在 1989 年圣诞节期间开始创造 Python&#xff0c;当时他就职于荷兰数学和计算机科学研究学会&#xff08;简称…

SAP ABAP CDS view 里 INNER JOIN 和 Association 的区别

最近有朋友在我的知识星球里向我提问&#xff0c;SAP ABAP CDS view 的 INNER JOIN 和 Association 的功能可以理解为一样吗&#xff1f; (关于加入我的知识星球的方式&#xff0c;请移步本文文末) 本文就来聊一聊这个话题。既然 CDS view 里同时支持了 INNER JOIN 和 Assoc…

小伙伴因 unshift 插入数据被批,未曾想到找我诉苦竟梅开二度

背景 事情是这样的&#xff0c;今天小伙伴跟我诉苦&#xff0c;说写的代码被批了&#xff0c;原因是效率太低了&#xff0c;简单问了一下需求&#xff0c;就是将几千条数据倒序插入到数组中&#xff0c;他是通过循环搭配 unshift 实现的&#xff0c;听完我也批了他一顿。 小伙…

ImageNet

标题有点不太对是的 就能用了 这次是说 用有噪声的学生网络进行自我训练提高ImageNet分类 近年来&#xff0c;深度学习在图像识别方面取得了显著的成功。然而&#xff0c;最先进的视觉模型仍然是用监督学习来训练的&#xff0c;这就需要大量的标记图像才能很好地工作。 通过只…

监控易火星版纳管IPv6:IP地址管理V2.0的进化之路

IPv6的使用&#xff0c;不仅能解决网络地址资源数量的问题&#xff0c;而且也解决了多种接入设备连入互联网的障碍。大量IP地址的接入&#xff0c;导致运维人员不得不投入大量精力来解决IP管理分散、非法接入、IP地址错误、IP地址冲突等导致的问题。随之而来的&#xff0c;IP地…

java计算机毕业设计基于安卓Android的校园助手APP

项目介绍 网络的广泛应用给生活带来了十分的便利。所以把校园助手与现在网络相结合,利用java技术建设校园助手APP,实现校园助手的信息化。则对于进一步提高校园助手发展,丰富校园助手经验能起到不少的促进作用。 校园助手APP能够通过互联网得到广泛的、全面的宣传,让尽可能多的…