Examples Bash, Part 3

Daniel Robbins in his last post Bash by example article detailed how Gentoo Linux ebuild system, an excellent example of the demonstrated power of bash. Step by step, he shows you how the ebuild system, and touched many convenient bash techniques and design strategies. At the end of this article, you will have a good grasp of manufacturing technology based entirely on the application involved bash, and start coding system for their automated build.

Into the ebuild system

I really have been looking forward to this third and final one Bash by example article, since it has been in since the first 1 and Part 2 describes the basics of programming in bash, you can focus on the like bash application development and program design which is more advanced Theme of. In this article, I spent a lot of time by coding and refining project, Gentoo Linux ebuild system, to give you a lot of practical, real-world experience in the development of bash.

I'm a Gentoo Linux (currently in beta next-generation Linux OS), chief designer. One of my main responsibility is to ensure that all binary packages (similar to RPM) are created properly and work together. As you may know, a standard Linux system is not a unified source tree composition (like BSD), but is actually made up more than 25 core work packages. These include:

package description
linux The actual kernel
util-linux Miscellaneous collection of Linux-related programs
e2fsprogs Utilities associated with the collection of ext2 file system
glibc The GNU C Library

Each package is in its own tar archive, the different independent developer or development team by maintenance. To create a distribution, you must download separately for each package, compiling and packaging process. Every time you want to repair, upgrade or improve the package, must be repeated compiling and packaging steps (and, indeed update package fast). To help eliminate duplicate steps to create and update packages involved, I created the ebuild system, the system is almost entirely written in bash. To increase your bash knowledge, I will show you step by step how to unpack and compile portions of the ebuild system. In explaining each step, we will discuss why certain design decisions to be made. At the end of this article, you will not only be an excellent grasp of large-scale bash programming projects, but also to achieve a significant part of a complete automated build system.





Back to top


Why bash?

Bash is an essential component Gentoo Linux ebuild system. Select it as ebuild's primary language for several reasons. First, the syntax is not complicated, and familiar to people, which is particularly suited for calling external programs. Automatic build system is automatically call an external program "veneers", and bash is very suitable for this type of application. Second, Bash ebuild support function allows the system modular, readable code. Third, ebuild system takes advantage of bash's support for environment variables, allowing package maintainers and developers to its convenient online configuration at run time.





Back to top


Build process review

Before discussing the ebuild system, let's review what compile and install the packages are involved. For example, let's look at the "sed" package, the stream editing utility for all GNU Linux version of the standard part of the text. First, download the source code tar archive (sed-3.02.tar.gz) (See Resources ). We will store this archive in / usr / src / distfiles, you will use the environment variable "$ DISTDIR" to refer to the directory. "$ DISTDIR" is all original source tar archive directory where the package, it is a large source code base.

The next step is to create a temporary directory called "work", the directory to store the uncompressed sources. After using the "$ WORKDIR" environment variable references the directory. To do this, enter the directory has write access, then enter:


The sed into a temporary directory
$ mkdir work
$ cd work
$ Takes xzf /usr/src/distfiles/sed-3.02.tar.gz

Then, decompress tar archive, created containing all source code directory called sed-3.02's. After using the environment variable "$ SRCDIR" reference sed-3.02 directory. To compile the program, enter:


The sed into a temporary directory
But CD-$ 3.02
$ ./configure --prefix=/usr
(Autoconf generates appropriate make file, it takes some time)
$ make
(Compiled from the source code package, also takes a bit of time)

Because in this article only describes unpack and compile steps, it will skip the "make install" step. If you are writing bash script to perform all of these steps, the code might look like:


To perform a sample bash script unpack / compile process
#!/usr/bin/env bash
if [ -d work ]
then 
# remove old work directory if it exists 
    rm -rf work
be
mkdir work
cd work
takes xzf /usr/src/distfiles/sed-3.02.tar.gz
But CD-3.02
./configure --prefix=/usr
make





Back to top


Generalizing the code

Although you can use this to automatically compile the script, but it's not very flexible. Basically, bash script contains only a list of all the commands on a command line. Although you can use this solution, however, is best done by changing only a few lines of script can be applied quickly unpack and compile any package. In this way, the package maintainer to add new packages to the desired release work is greatly reduced. Let us first try to use a number of different environmental variables to complete the build script more generic:


The new, more general script
#!/usr/bin/env bash
# P is the package name
But the P-3.02
# A is the archive name
A=${P}.tar.gz
export ORIGDIR=`pwd`
export WORKDIR=${ORIGDIR}/work
export SRCDIR=${WORKDIR}/${P}
if [ -z "$DISTDIR" ]
then 
    # set DISTDIR to /usr/src/distfiles if not already set
    DISTDIR=/usr/src/distfiles
be
export DISTDIR
if [ -d ${WORKDIR} ]
then    
    # remove old work directory if it exists 
    rm -rf ${WORKDIR}
be
mkdir ${WORKDIR}
cd ${WORKDIR}
tar xzf ${DISTDIR}/${A}
cd $ {SRCDIR}
./configure --prefix=/usr
make

已经向代码中添加了很多环境变量,但是,它基本上还是执行同一功能。但是,如果现在要要编译任何标准的 GNU 基于 autoconf 的源代码 tar 压缩包,只需简单地将该文件复制到一个新文件(用合适的名称来反映它所编译的新包名),然后将 "$A" 和 "$P" 的值更改成新值即可。所有其它环境变量都自动调整成正确设置,并且脚本按预想工作。虽然这很方便,但是代码还有改进余地。这段代码比我们开始创建的 "transcript" 脚本要长很多。既然任何编程项目的目标之一是减少用户复杂度,所以最好大幅度缩短代码,或者至少更好地组织代码。可以用一个巧妙的方法来做到这点 -- 将代码拆成两个单独文件。将该文件存为 "sed-3.02.ebuild":


sed-3.02.ebuild
#the sed ebuild file -- very simple!
P=sed-3.02
A=${P}.tar.gz
   

第一个文件不重要,只包含那些必须在每个包中配置的环境变量。下面是第二个文件,它包含操作的主要部分。将它存为 "ebuild",并使它成为可执行文件:


ebuild 脚本
#!/usr/bin/env bash
if [ $# -ne 1 ]
then 
    echo "one argument expected."
    exit 1
fi
if [ -e "$1" ]
then 
    source $1
else
    echo "ebuild file $1 not found."
    exit 1
fi
export ORIGDIR=`pwd`
export WORKDIR=${ORIGDIR}/work
export SRCDIR=${WORKDIR}/${P}
if [ -z "$DISTDIR" ]
then 
    # set DISTDIR to /usr/src/distfiles if not already set
    DISTDIR=/usr/src/distfiles
fi
export DISTDIR
if [ -d ${WORKDIR} ]
then    
    # remove old work directory if it exists 
    rm -rf ${WORKDIR}
fi
mkdir ${WORKDIR}
cd ${WORKDIR}
tar xzf ${DISTDIR}/${A}
cd ${SRCDIR}
./configure --prefix=/usr
make

既然已经将构建系统拆成两个文件,我敢打赌,您一定在想它的工作原理。基本上,要编译 sed,输入:

$ ./ebuild sed-3.02.ebuild

当执行 "ebuild" 时,它首先试图 "source" 变量 "$1"。这是什么意思?还记得 前一篇文章 所讲的吗:"$1" 是第一个命令行自变量 -- 在这里,是 "sed-3.02.ebuild"。在 bash 中,"source" 命令从文件中读入 bash 语句,然后执行它们,就好象它们直接出现在 "source" 命令所在的文件中一样。因此,"source ${1}" 导致 "ebuild" 脚本执行在 "sed-3.02.ebuild" 中定义 "$P" 和 "$A" 的命令。这种设计更改确实方便,因为如果要编译另一个程序,而不是 sed,可以简单地创建一个新的 .ebuild 文件,然后将其作为自变量传递给 "ebuild" 脚本。通过这种方式,.ebuild 文件最终非常简单,而将 ebuild 系统复杂的操作部分存在一处,即 "ebuild" 脚本中。通过这种方式,只需编辑 "ebuild" 脚本就可以升级或增强 ebuild 系统,同时将实现细节保留在 ebuild 文件之外。这里有一个 gzip 的样本 ebuild 文件:


gzip-1.2.4a.ebuild
#another really simple ebuild script!
P=gzip-1.2.4a
A=${P}.tar.gz





回页首


添加功能性

好,我们正在取得进展。但是,我还想添加某些额外功能性。我希望 ebuild 脚本再接受一个命令行自变量:"compile"、"unpack" 或 "all"。这个命令行自变量告诉 ebuild 脚本要执行构建过程的哪一步。通过这种方式,可以告诉 ebuild 解包档案,但不进行编译(以便在开始编译之前查看源代码档案)。要做到这点,将添加一条 case 语句,该语句将测试 "$2",然后根据其值执行不同操作。代码如下:


ebuild,修定本 2
#!/usr/bin/env bash
if [ $# -ne 2 ]
then 
    echo "Please specify two args - .ebuild file and unpack, compile or all"
    exit 1
fi
if [ -z "$DISTDIR" ]
then 
    # set DISTDIR to /usr/src/distfiles if not already set
    DISTDIR=/usr/src/distfiles
fi
export DISTDIR
ebuild_unpack() {
    #make sure we're in the right directory 
    cd ${ORIGDIR}
    
    if [ -d ${WORKDIR} ]
    then    
        rm -rf ${WORKDIR}
    fi
    mkdir ${WORKDIR}
    cd ${WORKDIR}
    if [ ! -e ${DISTDIR}/${A} ]
    then
        echo "${DISTDIR}/${A} does not exist.  Please download first."
        exit 1
    fi    
    tar xzf ${DISTDIR}/${A}
    echo "Unpacked ${DISTDIR}/${A}."
    #source is now correctly unpacked
}
ebuild_compile() {
    
    #make sure we're in the right directory
    cd ${SRCDIR}
    if [ ! -d "${SRCDIR}" ]
    then
        echo "${SRCDIR} does not exist -- please unpack first."
        exit 1
        fi
    ./configure --prefix=/usr
    make     
}
export ORIGDIR=`pwd`
export WORKDIR=${ORIGDIR}/work
if [ -e "$1" ]
then 
    source $1
else
    echo "Ebuild file $1 not found."
    exit 1
fi
export SRCDIR=${WORKDIR}/${P}
case "${2}" in
    unpack)
        ebuild_unpack
        ;;
    compile)
        ebuild_compile
        ;;
    all)
        ebuild_unpack
        ebuild_compile
        ;;
    *)
        echo "Please specify unpack, compile or all as the second arg"
        exit 1
        ;;
esac

已经做了很多改动,下面来回顾一下。首先,将编译和解包步骤放入各自的函数中,其函数名分别为 ebuild_compile() 和 ebuild_unpack()。这是个好的步骤,因为代码正变得越来越复杂,而新函数提供了一定的模块性,使代码更有条理。在每个函数的第一行,显式 "cd" 到想要的目录,因为,随着代码变得越来越模块化而不是线形化,出现疏忽而在错误的当前工作目录中执行函数的可能性也变大。"cd" 命令显式地使我们处于正确的位置,并防止以后出现错误 - 这是重要的步骤,特别是在函数中删除文件时更是如此。

另外,还在 ebuild_compile() 函数的开始处添加了一个有用的检查。现在,它检查以确保 "$SRCDIR" 存在,如果不存在,则打印一条告诉用户首先解包档案然后退出的错误消息。如果愿意,可以改变这种行为,以便在 "$SRCDIR" 不存在的情况下,ebuild 脚本将自动解包源代码档案。可以用以下代码替换 ebuild_compile() 来做到这点:


ebuild_compile() 上的新代码
ebuild_compile() {
    #make sure we're in the right directory  
    if [ ! -d "${SRCDIR}" ]
    then
        ebuild_unpack
        fi
    cd ${SRCDIR}
    ./configure --prefix=/usr
    make     
}

ebuild 脚本第二版中最明显的改动之一就是代码末尾新的 case 语句。这条 case 语句只是检查第二个命令行自变量,然后根据其值执行正确操作。如果现在输入:

$ ebuild sed-3.02.ebuild

就会得到一条错误消息。现在需要告诉 ebuild 做什么,如下所示:

$ ebuild sed-3.02.ebuild unpack

$ ebuild sed-3.02.ebuild compile

$ ebuild sed-3.02.ebuild all

如果提供上面所列之外的第二个命令行自变量,将得到一条错误消息(* 子句),然后,程序退出。





回页首


使代码模块化

既然代码很高级并且实用,您可能很想创建几个更高级的 ebuild 脚本,以解包和编译所喜爱的程序。如果这样做,迟早会遇到一些不使用 autoconf ("./configure") 的源代码,或者可能遇到其它使用非标准编译过程的脚本。需要再对 ebuild 系统做一些改动,以适应这些程序。但是在做之前,最好先想一下如何完成。

将 "./configure --prefix=/usr; make" 硬编码到编译阶段的妙处之一是:大多数时候,它可以正确工作。但是,还必须使 ebuild 系统适应那些不使用 autoconf 或正常 make 文件的源代码。要解决这个问题,建议 ebuild 脚本缺省执行以下操作:

  1. 如果在 "${SRCDIR}" 中有一个配置脚本,则按如下执行它:
    ./configure --prefix=/usr
    否则,跳过这步。
  2. 执行以下命令:
    make

既然 ebuild 只在 configure 实际存在时才运行它,现在可以自动地适应那些不使用 autoconf 但有标准 make 文件的程序。但是,在简单的 "make" 对某些源代码无效时该怎么办?需要一些处理这些情况的特定代码来覆盖合理的缺省值。要做到这一点,将把 ebuild_compile() 函数转换成两个函数。第一个函数(可将其当成“父”函数)的名称仍是 ebuild_compile()。但是,将有一个名为 user_compile() 的新函数,该函数只包含合理的缺省操作:


拆成两个函数的 ebuild_compile()
user_compile() {
    #we're already in ${SRCDIR}
    if [ -e configure ]
    then
        #run configure script if it exists
        ./configure --prefix=/usr
    fi
    #run make
    make
}          
ebuild_compile() {
    if [ ! -d "${SRCDIR}" ]
    then
        echo "${SRCDIR} does not exist -- please unpack first."
        exit 1
    fi
    #make sure we're in the right directory  
    cd ${SRCDIR}
    user_compile
}

现在这样做的原因可能还不是很明显,但是,再忍耐一下。虽然这段代码与 ebuild 前一版的工作方式几乎相同,但是现在可以做一些以前无法做的 -- 可以在 sed-3.02.ebuild 中覆盖 user_compile()。因此,如果缺省的 user_compile() 不满足要求,可以在 .ebuild 文件中定义一个新的,使其包含编译包所必需的命令。例如,这里有一个 e2fsprogs-1.18 的 ebuild 文件,它需要一个略有不同的 "./configure" 行:


e2fsprogs-1.18.ebuild
#this ebuild file overrides the default user_compile()
P=e2fsprogs-1.18
A=${P}.tar.gz
 
user_compile() {
       ./configure --enable-elf-shlibs
       make
}

现在,将完全按照我们希望的方式编译 e2fsprogs。但是,对于大多数包来说,可以省略 .ebuild 文件中的任何定制 user_compile() 函数,而使用缺省的 user_compile() 函数。

ebuild 脚本又怎样知道要使用哪个 user_compile() 函数呢?实际上,这很简单。ebuild 脚本中,在执行 e2fsprogs-1.18.ebuild 文件之前定义缺省 user_compile() 函数。如果在 e2fsprogs-1.18.ebuild 中有一个 user_compile(),则它覆盖前面定义的缺省版本。如果没有,则使用缺省 user_compile() 函数。

这是好工具,我们已经添加了很多灵活性,而无需任何复杂代码(如果不需要的话)。在这里就不讲了,但是,还应该对 ebuild_unpack() 做类似修改,以便用户可以覆盖缺省解包过程。如果要做任何修补,或者文件包含在多个档案中,则这非常方便。还有个好主意是修改解包代码,以便它可以缺省识别由 bzip2 压缩的 tar 压缩包。





回页首


配置文件

目前为止,已经讲了很多不方便的 bash 技术,现在再讲一个。通常,如果程序在 /etc 中有一个配置文件是很方便的。幸运的是,用 bash 做到这点很容易。只需创建以下文件,然后并其存为 /etc/ebuild.conf 即可:


/ect/ebuild.conf
# /etc/ebuild.conf: set system-wide ebuild options in this file
# MAKEOPTS are options passed to make
MAKEOPTS="-j2"

在该例中,只包括了一个配置选项,但是,您可以包括更多。bash 的一个妙处是:通过执行该文件,就可以对它进行语法分析。在大多数解释型语言中,都可以使用这个设计窍门。执行 /etc/ebuild.conf 之后,在 ebuild 脚本中定义 "$MAKEOPTS"。将利用它允许用户向 make 传递选项。通常,将使用该选项来允许用户告诉 ebuild 执行 并行 make





回页首


什么是并行 make?

为了提高多处理器系统的编译速度,make 支持并行编译程序。这意味着,make 同时编译用户指定数目的源文件(以便使用多处理器系统中的额外处理器),而不是一次只编译一个源文件。通过向 make 传递 -j # 选项来启用并行 make,如下所示:

make -j4 MAKE="make -j4"

这行代码指示 make 同时编译四个程序。 MAKE="make -j4" 自变量告诉 make,向其启动的任何子 make 进程传递 -j4 选项。

这里是 ebuild 程序的最终版本:


ebuild,最终版本
#!/usr/bin/env bash
if [ $# -ne 2 ]
then 
    echo "Please specify ebuild file and unpack, compile or all"
    exit 1
fi
source /etc/ebuild.conf
if [ -z "$DISTDIR" ]
then 
    # set DISTDIR to /usr/src/distfiles if not already set
    DISTDIR=/usr/src/distfiles
fi
export DISTDIR
ebuild_unpack() {
    #make sure we're in the right directory 
    cd ${ORIGDIR}
    
    if [ -d ${WORKDIR} ]
    then    
        rm -rf ${WORKDIR}
    fi
    mkdir ${WORKDIR}
    cd ${WORKDIR}
    if [ ! -e ${DISTDIR}/${A} ]
    then
        echo "${DISTDIR}/${A} does not exist.  Please download first."
        exit 1
    fi
    tar xzf ${DISTDIR}/${A}
    echo "Unpacked ${DISTDIR}/${A}."
    #source is now correctly unpacked
}
user_compile() {
    #we're already in ${SRCDIR}
    if [ -e configure ]
    then
        #run configure script if it exists
        ./configure --prefix=/usr
    fi
        #run make
        make $MAKEOPTS MAKE="make $MAKEOPTS"  
} 
ebuild_compile() {
    if [ ! -d "${SRCDIR}" ]
    then
        echo "${SRCDIR} does not exist -- please unpack first."
        exit 1
    fi
        #make sure we're in the right directory  
    cd ${SRCDIR}
    user_compile
}
export ORIGDIR=`pwd`
export WORKDIR=${ORIGDIR}/work
if [ -e "$1" ]
then 
    source $1
else
    echo "Ebuild file $1 not found."
    exit 1
fi
export SRCDIR=${WORKDIR}/${P}
case "${2}" in
    unpack)
        ebuild_unpack
        ;;
    compile)
        ebuild_compile
        ;;
    all)
        ebuild_unpack
        ebuild_compile
        ;;
    *)
        echo "Please specify unpack, compile or all as the second arg"
        exit 1
        ;;
esac

请注意,在文件的开始部分执行 /etc/ebuild.conf。另外,还要注意,在缺省 user_compile() 函数中使用 "$MAKEOPTS"。您可能在想,这管用吗 - 毕竟,在执行实际上事先定义 "$MAKEOPTS" 的 /etc/ebuild.conf 之前,我们引用了 "$MAKEOPTS"。对我们来说幸运的是,这没有问题,因为变量扩展只在执行 user_compile() 时才发生。在执行 user_compile() 时,已经执行了 /etc/ebuild.conf,并且 "$MAKEOPTS" 也被设置成正确的值。





回页首


结束语

本文已经讲述了很多 bash 编程技术,但是,只触及到 bash 能力的一些皮毛。例如,Gentoo Linux ebuild 产品不仅自动解包和编译每个包,还可以:

  • 如果在 "$DISTDIR" 没找到源代码,则自动下载
  • 通过使用 MD5 消息摘要,验证源代码没有受到破坏
  • 如果请求,则将编译过的应用程序安装到正在使用的文件系统,并记录所有安装的文件,以便日后可以方便地将包卸载。
  • 如果请求,则将编译过的应用程序打包成 tar 压缩包(以您希望的形式压缩),以便以后可以在另一台计算机上,或者在基于 CD 的安装过程中(如果在构建发行版 CD)安装。

另外,ebuild 系统产品还有几个全局配置选项,允许用户指定选项,例如在编译过程中使用什么优化标志,在那些支持它的包中是否应该缺省启用可选的包支持(例如 GNOME 和 slang)。

显然,bash 可以实现的功能远比本系列文章中所触及的要多。关于这个不可思议的工具,希望您已经学到了很多,并鼓舞您使用 bash 来加快和增强开发项目。



参考资料



关于作者

作者

Daniel Robbins 居住在美国新墨西哥州 Albuquerque。他是 Gentoo 项目 的总设计师,Gentoo Technologies, Inc. 的 CEO,Linux Advanced Multimedia Project (LAMP) 的顾问,Macmillan 书籍 Caldera OpenLinux UnleashedSuSE Linux UnleasedSamba Unleashed 的作者。Daniel 自小学二年级起就与计算机结下不解之缘,那时他首先接触的是 Logo 程序语言,并沉溺于 Pac-Man 游戏中。这也许就是他至今仍担任 SONY Electronic Publishing/ Psygnosis 首席图形设计师的原因所在。Daniel 喜欢与妻子 Mary 一起共渡时光,他们的孩子即将诞生。可通过 [email protected] 与 Daniel Robbins 联系。

转载于:https://www.cnblogs.com/licheng/archive/2008/08/05/1261063.html

Guess you like

Origin blog.csdn.net/weixin_34128839/article/details/92631384