Shell Script Learning Guide (7) - Generating Scripts


foreword

In this article, we will proceed to more complex tasks. We consider the examples presented here to be commonly available tools, each of which is distinct and not found in most UNIX tool sets.

Programs in this chapter, including command-line argument analysis, computing on remote hosts, environment variables, job logging, parallel processing, runtime statement execution used, evalscript files, shell functions, user-defined initialization files, and security considerations example of . The program uses important statements in the shell language and exhibits traditional UNIX shell scripting style.

path finding

Some programs support looking for input files on a directory path, kind of like a UNIX shell looks for a colon-separated list of directories, PATHlisted , to find an executable program. This is convenient for the user, who only needs to provide shorter filenames and does not need to know where they are located in the file system. UNIX does not mention any special commands or system calls that can find files in the search path, even though other operating systems supported this feature long ago. Fortunately, it is not difficult to do path finding, as long as you use the right tool.

Instead of implementing path lookup to find a specific program, make a new tool that takes as argument a variable traversal name where the environment variable name expansion is the expected lookup path, followed by zero or more file patterns, and reports the Location. Our program will become a general tool in other software that needs pathfinding support (this is also the principle of "before you build a specific tool, imagine it" we mentioned earlier).

Sometimes you have to know whether there is more than one file in the path, because when there are different versions in the path, you may need to adjust the path to control the version to be found. Our program gives the user a command-line option to choose between reporting the first file found or all files found. In addition, an identifiable version number is provided according to user requirements, which becomes the standard implementation of the software, and short online help must be provided, so that users do not need to reread the program's manual every time they use the program to remember the name of the option . Of course, our program also provides such a function.

We first analyze the pseudo-code program, which is regarded as the sequence of comments and descriptions of each fragment of the Shell program code, for the sake of explanation.

We start with a general introductory comment block. The first line identifies the magic line of the program /bin/shto execute the script. After the comment block, it is followed by a description of the program's behavior and instructions on how to use it:

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

In the network environment, security has always been a problem that must be carefully considered. One of the ways to attack shell scripts is to use the input field separator character: IFS, which affects the way the shell interprets the input data next. IFSTo avoid this type of attack, some shells reset to a standard value just before script execution ; others import an external setting for the variable. We put our own preventive operations in the first step of the script:

IFS='

It's hard to see on the screen or on the displayed page what's inside the quotes: they're a three-character string, incl 换行字符、空格、以及定位字符(tab). The default value of IFS is: 空格,定位字符、换行符, but if we write it this way, those editors that automatically trim blanks may truncate the trailing blanks, reducing the value of the string to only a newline character. A better way should be to use escape characters more strictly, for example IFS="\040\t\n", but Bourne Shell does not support such escape characters.

Please pay special attention to one point when we redefine IFS. When "$*"expanded to restore the command line, the first character of the IFS value is treated as a field separator. We don't use it in this script "$*", so rearranging the characters inside IFS won't affect it.

Another common security attack is tricking software into executing commands we didn't expect. In order to block this attack, we hope that the invoked program is a trusted version, not a cheat program lurking in the search path provided by the user, so we reset PATH to a minimum value to store the initial value for later use :

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

exportstatement is the key here: it ensures that all child processes inherit our safe lookup path.

The program code is followed by 5 short functions in alphabetical order.

The first is error()a function that prints its arguments on standard error, and calls a function (described later in this section) that does not return:

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

The second function usage()writes a short message showing how the program is being used, and returns it to its caller. Note: this function expects the program name, but not in direct coding mode, it PROGRAMgets the program name from a variable that is set to the name of the program being called. This allows the installer to rename programs without modifying the program code, which often happens when there is a name conflict with an already installed program, that is, when it has the same name but has a different purpose. The function itself is simple:

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

The third function usage_and_exit(), produces syntax information, and exits with the status code provided by its single argument:

usage_and_exit()
{
    
    
    usage
    exit $1
}

The fourth parameter version()is to display the program version number on standard output and return to its caller. As such usage(), it is obtained using PROGRAMthe program name:

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

The last function warnning()prints its argument on standard error, EXITCODEincrements a variable, records the number of warnings issued, and returns it to the caller:

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

EXITCODEThe auto-increment operation can be used in POSIX EXITCODE=$((EXITCODE + 1)), but there are still quite a few systems that do not recognize this usage of POSIX.

Even if the program is short, we don't need to write functions at all. In addition to avoiding program code duplication, it can actually hide irrelevant details. This is a good program implementation: tell us what we are going to do, not how we are going to do it .

At this point we have reached the first executed statement of the runtime. First initialize 5 variables to record the selection of options, user-supplied environment variable names, exit codes, program names, and program version numbers:

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

In our program, it will follow that lowercase (letter) variables are used by local functions or the body of the main program code, while uppercase variables are globally shared by the entire program. The all variable here is given a string value instead of a number because it makes the program clearer and has a negligible impact on runtime resource consumption.

Next comes the large code block, which is typical of command-line argument parsing in all UNIX programs: when we have an argument (determined by the argument count value, which must be greater than zero), statements are chosen based on the argument's string $#value caseblock, handle the function:

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

caseSelectors may be interpreted differently in different contexts. The GNU programming style encourages the use of long, descriptive option names, rather than the old, cryptic set of single-character options long used in UNIX. The latter succinct approach is acceptable when the number of options is small and the program is used frequently. If this is not the case, a descriptive name will be better, and the user only needs to provide enough information - not to be confused with other The options are repeated. However, this shorthand should be avoided when other programs offer the same options. Doing so makes it easier for users to understand the program and ensures that future versions of the program will add new options without unexpected results.

For options, we simply record the fact that an option was found --allby resetting the variable all to :yes

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

--version、--help同理

caseThe selector -*)matches all remaining options: we report the invalid option on stderr, call usage()the function, alert the user of the usage, and exit immediately with a failure status code (1):

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

Here, the match indicates that we have processed all options, so we can exit the loop. Since we've dealt with all the cases, here's the final keyword to end casethe statement:

*)
    break
    ;;
esac

pathfindThe complete content of the program

#! /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层循环

Test Case:
insert image description here
insert image description here

pathfindA useful exercise indeed. In addition to being a handy new utility program that isn't in the standard GNU, POSIX, and UNIX toolsets, it has all the major building blocks of most UNIX programs: argument parsing, option handling, error reporting, and data handling. We also illustrate a three-step process for eliminating several well-known security holes: Adding -the option terminates the starting shell command line, setting now IFS及PATH. The advantage of this program code is that it can be reused, and only a little modification is required, such as: pre-comment flags, allocation of IFS and PATH, 5 auxiliary functions, while and case statements for processing parameters, and at least the outer loop will traverse Collect files on the command line.

As an exercise, you can start to consider whether you should pathfindmake some changes for these extensions:

  • Store the redirection of stdout and stderr to /dev/null, with --quietthe option to suppress all output, so the only thing seen is to indicate whether a matching exit code was found. This useful program function is already available in the options cmpof -sand .grep-q

  • With --traceoptions, respond to standard error with the full path of each file to be tested.

  • Plus --test xoptions, so that test的-f选项it can be replaced with other values, such as -h(文件为符号性连接)、-r(文件是可读取的)、-x(文件是可具执行的)etc.

  • Let pathfindacts as a filter: if no filename is specified on the command line, it should read the list of files from standard input. How does this affect the structure and organization of the program?

  • Patch all security holes you can find, such as issues listed in the latest security bulletins.

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

Software Build Automation

Since UNIX can run on a variety of platforms, when building a software package, the common implementation is to install it from the source code instead of directly installing the binary package. Large-scale UNIX sites are often combined by several platforms. For administrators, the most tedious and troublesome work is to install packages on these different systems. And that's where the opportunity for automation comes in. Many software developers have directly adopted the conventions of packages developed under the GNU Project, including:

  • The package is package-x.y.z.tar,gz(或package-x.y.z.tar.bz2)distributed as a compressed archive file, which will appear in package-x.y.zthe directory after unpacking.

  • Top-level configurescripts are usually generated automatically by GNU autoconfcommands through configure.in或configure.aca list of rules in the file. When the script is executed, some command-line options are sometimes added to generate a custom C/C++ header file, usually called config.h、衍生自Makefile.ina customization (template file) Makefile, and sometimes other files.

  • The standard set of Makefile targets (target) has been detailed in "The GNU Coding Standards", there are all (all builds), check (perform verification tests), clean (delete unnecessary intermediate files), distclean (restore directory to it original release), and install (installs all required files on the local system).

  • The installed files are located in the default tree directory defined by the prefix variable in the Makefile, and can be --prefix=dirset using command-line options at configure time, or provided through a local system-wide customization file. The default prefix为/usr/local, but unprivileged users may have to use it $HOME/local, or better yet, use $HOME/``arch/local. Among them archis a command that displays a short description of the defined platform. GNU/Linux and Sun Solaris provide yes /bin/arch. On other platforms, when installing your own implementation, you usually just use a simple shell script wrapper ( wrapper) with the appropriate echocommands.

The next thing to do is to build scripts that, given a list of packages, find their source distributions in one of the many standard locations under the current system, copy them to each of the list of remote hosts, where to unpack them , then compile and make it legally usable. We've found that automating the installation step is not smart: build logs must be carefully inspected first.

This script must be available to all users on the UNIX site, so we cannot embed information about specific hosts in it. We assume that the user has provided two customization files: directories——列出要查找包分发文件的位置,以及userhosts——列出用户名称、远程主机名称、远程构建目录以及特殊环境变量. We put these and other related files in a hidden directory $HOME/.buildto reduce clutter. However, since the source directory listings are likely to be similar for all users within the same site, we include a reasonable default list, eliminating the need for files directories.

Sometimes builds need to be able to be done on only a subset of the common build hosts, or use archive files that are not in a common location, so scripts need to be able to set these values ​​on the command line.

The script we developed here can be called like this:

$ 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  #从非标准位置中构建包

These commands actually do a lot of things. Below we outline the steps to process each specified package and install it on the default or selected build host:

  1. Look for the package distribution file under the local file system.

  2. Copy the distribution files to the remote build host.

  3. Initializes a login connection on the remote host.

  4. Change to the remote build directory, and unpack the distribution files.

  5. Change to the package build directory and set, build and test the package.

  6. Will initialize all output on the host, for each package and build environment, in separate log files.

The builds on the remote hosts are performed in parallel, so the total time required for installation execution is based on the slowest machine, rather than summing all individual times. For those of us who often install more than a hundred different environment systems, fortunately there are build-allprograms, which also provide a good challenge for package developers.

The complete procedure is as follows:

#! /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

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

Guess you like

Origin blog.csdn.net/m0_56145255/article/details/128305360