探讨 ebuild 系统
Daniel Robbins 在其最后一篇 Bash实例文章中详细讲述了 Gentoo Linux ebuild 系统,这个展示 bash能力的极佳范例。循序渐进地,他为您展示如何实现 ebuild系统,并触及很多方便的 bash技术和设计策略。在本文末尾,您将很好地掌握制造完全基于 bash的应用所涉及的技术,并开始为自己的自动构建系统编码。
我真是一直期待着这第三篇、也是最后一篇 Bash 实例文章,因为既然已经在 第 1 篇和 第 2 篇 中讲述了 bash 编程基础,就可以集中讲述象 bash 应用开发和程序设计这样更高级的主题。在本文中,将通过我花了许多时间来编码和细化的项目,Gentoo Linux ebuild 系统,来给您大量实际的、现实世界的 bash 开发经验。
我是 Gentoo Linux(目前还是 beta 版的下一代 Linux OS)的首席设计师。我的主要责任之一就是确保所有二进制包(类似于 RPM)都正确创建并一起使用。正如您可能知道的,标准 Linux 系统不是由一棵统一的源树组成(象 BSD),而实际上是由超过 25 个协同工作的核心包组成。这其中包括:
包 描述
linux 实际内核
util-linux 与 Linux 相关的杂项程序集合
e2fsprogs 与 ext2 文件系统相关的实用程序集合
glibc GNU C 库
每个包都位于各自的 tar 压缩包中,并由不同的独立开发人员或开发小组维护。要创建一个发行版,必须对每个包分别进行下载、编译和打包处理。每次要修复、升级或改进包时,都必须重复编译和打包步骤(并且,包确实更新得很快)。为了帮助消除创建和更新包所涉及的重复步骤,我创建了 ebuild 系统,该系统几乎全用 bash 编写。为了增加您的 bash 知识,我将循序渐进地为您演示如何实现该 ebuild 系统的解包和编译部分。在解释每一步时,还将讨论为什么要作出某些设计决定。在本文末尾,您不仅将极好地掌握大型 bash 编程项目,还实现了完整自动构建系统的很大一部分。
Bash 是 Gentoo Linux ebuild 系统的基本组件。选择它做为 ebuild 的主要语言有几个原因。首先,其语法不复杂,并且为人们所熟悉,这特别适合于调用外部程序。自动构建系统是自动调用外部程序的“胶合代码”,而 bash 非常适合于这种类型的应用。第二,Bash 对函数的支持允许 ebuild 系统使用模块化、易于理解的代码。第三,ebuild 系统利用了 bash 对环境变量的支持,允许包维护人员和开发人员在运行时对其进行方便的在线配置。
在讨论 ebuild 系统之前,让我们回顾一下编译和安装包都牵涉些什么。例如,让我们看一下 "sed" 包,这个作为所有 Linux 版本一部分的标准 GNU 文本流编辑实用程序。首先,下载源代码 tar 压缩包 (sed-3.02.tar.gz)(请参阅 参考资料 )。我们将把这个档案存储在 /usr/src/distfiles 中,将使用环境变量 "$DISTDIR" 来引用该目录。"$DISTDIR" 是所有原始源代码 tar 压缩包所在的目录,它是一个大型源代码库。
下一步是创建名为 "work" 的临时目录,该目录存放已经解压的源代码。以后将使用 "$WORKDIR" 环境变量引用该目录。要做到这点,进入有写权限的目录,然后输入:
$ mkdir work
$ cd work
$ tar xzf /usr/src/distfiles/sed-3.02.tar.gz
然后,解压缩 tar 压缩包,创建一个包含所有源代码、名为 sed-3.02 的目录。以后将使用环境变量 "$SRCDIR" 引用 sed-3.02 目录。要编译程序,输入:
$ cd sed-3.02
$ ./configure --prefix=/usr
(autoconf 生成适当的 make 文件,这要花一些时间)
$ make
(从源代码编译包,也要花一点时间)
因为在本文中只讲述解包和编译步骤,所以将略过 "make install" 步骤。如果要编写 bash 脚本来执行所有这些步骤,则代码可能类似于:
#!/usr/bin/env bash
if [ -d work ]
then
# remove old work directory if it exists
rm -rf work
fi
mkdir work
cd work
tar xzf /usr/src/distfiles/sed-3.02.tar.gz
cd sed-3.02
./configure --prefix=/usr
make
虽然可以使用这个自动编译脚本,但它不是很灵活。基本上,bash 脚本只包含在命令行输入的所有命令列表。虽然可以使用这种解决方案,但是,最好做一个只通过更改几行就可以快速解包和编译任何包的适用脚本。这样,包维护人员将新包添加到发行版所需的工作就大为减少。让我们先尝试一下使用许多不同的环境变量来完成,使构建脚本更加适用:
#!/usr/bin/env bash
# P is the package name
P=sed-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
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
已经向代码中添加了很多环境变量,但是,它基本上还是执行同一功能。但是,如果现在要要编译任何标准的 GNU 基于 autoconf 的源代码 tar 压缩包,只需简单地将该文件复制到一个新文件(用合适的名称来反映它所编译的新包名),然后将 "$A" 和 "$P" 的值更改成新值即可。所有其它环境变量都自动调整成正确设置,并且脚本按预想工作。虽然这很方便,但是代码还有改进余地。这段代码比我们开始创建的 "transcript" 脚本要长很多。既然任何编程项目的目标之一是减少用户复杂度,所以最好大幅度缩短代码,或者至少更好地组织代码。可以用一个巧妙的方法来做到这点 -- 将代码拆成两个单独文件。将该文件存为 "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 文件:
#another really simple ebuild script!
P=gzip-1.2.4a
A=${P}.tar.gz
好,我们正在取得进展。但是,我还想添加某些额外功能性。我希望 ebuild 脚本再接受一个命令行自变量:"compile"、"unpack" 或 "all"。这个命令行自变量告诉 ebuild 脚本要执行构建过程的哪一步。通过这种方式,可以告诉 ebuild 解包档案,但不进行编译(以便在开始编译之前查看源代码档案)。要做到这点,将添加一条 case 语句,该语句将测试 "$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() {
#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 脚本缺省执行以下操作:
如果在 "${SRCDIR}" 中有一个配置脚本,则按如下执行它:
./configure --prefix=/usr
否则,跳过这步。
执行以下命令:
make
既然 ebuild 只在 configure 实际存在时才运行它,现在可以自动地适应那些不使用 autoconf 但有标准 make 文件的程序。但是,在简单的 "make" 对某些源代码无效时该怎么办?需要一些处理这些情况的特定代码来覆盖合理的缺省值。要做到这一点,将把 ebuild_compile() 函数转换成两个函数。第一个函数(可将其当成“父”函数)的名称仍是 ebuild_compile()。但是,将有一个名为 user_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" 行:
#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 传递 -j # 选项来启用并行 make,如下所示:
make -j4 MAKE="make -j4"
这行代码指示 make 同时编译四个程序。 MAKE="make -j4" 自变量告诉 make,向其启动的任何子 make 进程传递 -j4 选项。
这里是 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 在其最后一篇 Bash实例文章中详细讲述了 Gentoo Linux ebuild 系统,这个展示 bash能力的极佳范例。循序渐进地,他为您展示如何实现 ebuild系统,并触及很多方便的 bash技术和设计策略。在本文末尾,您将很好地掌握制造完全基于 bash的应用所涉及的技术,并开始为自己的自动构建系统编码。
我真是一直期待着这第三篇、也是最后一篇 Bash 实例文章,因为既然已经在 第 1 篇和 第 2 篇 中讲述了 bash 编程基础,就可以集中讲述象 bash 应用开发和程序设计这样更高级的主题。在本文中,将通过我花了许多时间来编码和细化的项目,Gentoo Linux ebuild 系统,来给您大量实际的、现实世界的 bash 开发经验。
我是 Gentoo Linux(目前还是 beta 版的下一代 Linux OS)的首席设计师。我的主要责任之一就是确保所有二进制包(类似于 RPM)都正确创建并一起使用。正如您可能知道的,标准 Linux 系统不是由一棵统一的源树组成(象 BSD),而实际上是由超过 25 个协同工作的核心包组成。这其中包括:
包 描述
linux 实际内核
util-linux 与 Linux 相关的杂项程序集合
e2fsprogs 与 ext2 文件系统相关的实用程序集合
glibc GNU C 库
每个包都位于各自的 tar 压缩包中,并由不同的独立开发人员或开发小组维护。要创建一个发行版,必须对每个包分别进行下载、编译和打包处理。每次要修复、升级或改进包时,都必须重复编译和打包步骤(并且,包确实更新得很快)。为了帮助消除创建和更新包所涉及的重复步骤,我创建了 ebuild 系统,该系统几乎全用 bash 编写。为了增加您的 bash 知识,我将循序渐进地为您演示如何实现该 ebuild 系统的解包和编译部分。在解释每一步时,还将讨论为什么要作出某些设计决定。在本文末尾,您不仅将极好地掌握大型 bash 编程项目,还实现了完整自动构建系统的很大一部分。
Bash 是 Gentoo Linux ebuild 系统的基本组件。选择它做为 ebuild 的主要语言有几个原因。首先,其语法不复杂,并且为人们所熟悉,这特别适合于调用外部程序。自动构建系统是自动调用外部程序的“胶合代码”,而 bash 非常适合于这种类型的应用。第二,Bash 对函数的支持允许 ebuild 系统使用模块化、易于理解的代码。第三,ebuild 系统利用了 bash 对环境变量的支持,允许包维护人员和开发人员在运行时对其进行方便的在线配置。
在讨论 ebuild 系统之前,让我们回顾一下编译和安装包都牵涉些什么。例如,让我们看一下 "sed" 包,这个作为所有 Linux 版本一部分的标准 GNU 文本流编辑实用程序。首先,下载源代码 tar 压缩包 (sed-3.02.tar.gz)(请参阅 参考资料 )。我们将把这个档案存储在 /usr/src/distfiles 中,将使用环境变量 "$DISTDIR" 来引用该目录。"$DISTDIR" 是所有原始源代码 tar 压缩包所在的目录,它是一个大型源代码库。
下一步是创建名为 "work" 的临时目录,该目录存放已经解压的源代码。以后将使用 "$WORKDIR" 环境变量引用该目录。要做到这点,进入有写权限的目录,然后输入:
$ mkdir work
$ cd work
$ tar xzf /usr/src/distfiles/sed-3.02.tar.gz
然后,解压缩 tar 压缩包,创建一个包含所有源代码、名为 sed-3.02 的目录。以后将使用环境变量 "$SRCDIR" 引用 sed-3.02 目录。要编译程序,输入:
$ cd sed-3.02
$ ./configure --prefix=/usr
(autoconf 生成适当的 make 文件,这要花一些时间)
$ make
(从源代码编译包,也要花一点时间)
因为在本文中只讲述解包和编译步骤,所以将略过 "make install" 步骤。如果要编写 bash 脚本来执行所有这些步骤,则代码可能类似于:
#!/usr/bin/env bash
if [ -d work ]
then
# remove old work directory if it exists
rm -rf work
fi
mkdir work
cd work
tar xzf /usr/src/distfiles/sed-3.02.tar.gz
cd sed-3.02
./configure --prefix=/usr
make
虽然可以使用这个自动编译脚本,但它不是很灵活。基本上,bash 脚本只包含在命令行输入的所有命令列表。虽然可以使用这种解决方案,但是,最好做一个只通过更改几行就可以快速解包和编译任何包的适用脚本。这样,包维护人员将新包添加到发行版所需的工作就大为减少。让我们先尝试一下使用许多不同的环境变量来完成,使构建脚本更加适用:
#!/usr/bin/env bash
# P is the package name
P=sed-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
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
已经向代码中添加了很多环境变量,但是,它基本上还是执行同一功能。但是,如果现在要要编译任何标准的 GNU 基于 autoconf 的源代码 tar 压缩包,只需简单地将该文件复制到一个新文件(用合适的名称来反映它所编译的新包名),然后将 "$A" 和 "$P" 的值更改成新值即可。所有其它环境变量都自动调整成正确设置,并且脚本按预想工作。虽然这很方便,但是代码还有改进余地。这段代码比我们开始创建的 "transcript" 脚本要长很多。既然任何编程项目的目标之一是减少用户复杂度,所以最好大幅度缩短代码,或者至少更好地组织代码。可以用一个巧妙的方法来做到这点 -- 将代码拆成两个单独文件。将该文件存为 "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 文件:
#another really simple ebuild script!
P=gzip-1.2.4a
A=${P}.tar.gz
好,我们正在取得进展。但是,我还想添加某些额外功能性。我希望 ebuild 脚本再接受一个命令行自变量:"compile"、"unpack" 或 "all"。这个命令行自变量告诉 ebuild 脚本要执行构建过程的哪一步。通过这种方式,可以告诉 ebuild 解包档案,但不进行编译(以便在开始编译之前查看源代码档案)。要做到这点,将添加一条 case 语句,该语句将测试 "$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() {
#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 脚本缺省执行以下操作:
如果在 "${SRCDIR}" 中有一个配置脚本,则按如下执行它:
./configure --prefix=/usr
否则,跳过这步。
执行以下命令:
make
既然 ebuild 只在 configure 实际存在时才运行它,现在可以自动地适应那些不使用 autoconf 但有标准 make 文件的程序。但是,在简单的 "make" 对某些源代码无效时该怎么办?需要一些处理这些情况的特定代码来覆盖合理的缺省值。要做到这一点,将把 ebuild_compile() 函数转换成两个函数。第一个函数(可将其当成“父”函数)的名称仍是 ebuild_compile()。但是,将有一个名为 user_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" 行:
#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 传递 -j # 选项来启用并行 make,如下所示:
make -j4 MAKE="make -j4"
这行代码指示 make 同时编译四个程序。 MAKE="make -j4" 自变量告诉 make,向其启动的任何子 make 进程传递 -j4 选项。
这里是 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 来加快和增强开发项目。
发表评论
-
linux oracle 11g install
2015-08-13 13:47 663centos 5.10 下安装oracle 11g_r2 ... -
linux 虚拟机复制后网络无法重启device eth does not seem to be present
2014-07-06 00:28 748vmlite虚拟机启动出错,就把这个虚拟机删除掉重新建立,系 ... -
远程启图像界面登录linux
2014-07-04 15:14 822首先的配置本地yum/etc/yum.conf[Serve ... -
oracle11g redhat6
2014-07-04 15:13 2023red hat enterprise 6安装 挂载光驱 ... -
oracle11g安装Centos
2014-07-04 15:10 742linux64位系统设置/etc/hosts文件 fo ... -
linux 本地源创建redhat enterprise 6
2014-07-04 03:06 815如何解决 yum安装出现This system is no ... -
linux rpm 依赖性安装
2013-07-03 18:51 652yum --disablerepo=\* --enabler ... -
linux umask介绍
2013-03-28 12:49 606umask为权限掩码 一般和chmod配套使用 设置文件的 ... -
linux服务启动优化配置
2012-06-05 14:26 1527本机服务参考: chkconfig --level 2345 ... -
myeclipse 下载地址
2012-06-05 12:02 500下面是MyEclipse 8.5官方下载地址: 请在IE下 ... -
bash1
2012-04-22 02:38 573Bourne again shell (bash) 基 ... -
bash2
2012-04-22 02:38 564在前一篇 bash 的介绍性 ... -
crontab
2012-04-22 02:39 843crontab命令的功能是在一定的时间间隔调度一些命令的执行。 ... -
shell8
2012-04-25 22:38 635Linux shell脚本前面的实例是说明十进制和二进制的转换 ... -
shell7
2012-04-25 22:38 663Linux shell脚本基础学习这部分如果只看前面间的理论部 ... -
shell5
2012-04-25 22:39 667Linux shell脚本基础已经被分成好几个部分了,这里对控 ... -
shell4
2012-04-25 22:39 676上一篇Linux shell脚本基础学习中我们讲了Linux ... -
shell3
2012-03-20 17:29 527Linux shell脚本基础学习 ... -
shell2
2012-03-20 17:28 570Linux shell脚本基础课程前面一讲介绍的都是语法基础的 ... -
shell1
2012-03-20 17:27 603Linux shell脚本基础学习 ...
相关推荐
**Python-bash3boilerplate** 是一个专为Python开发者设计的工具,旨在帮助他们创建高效、可维护的Bash脚本。这个项目的核心理念是将Python的开发规范和最佳实践应用到Bash脚本中,提升脚本的可读性和可维护性,尤其...
在Linux和Unix系统中,Bash Shell是一种广泛使用的命令行解释器,它是GNU项目的一部分,由Brian Fox在1987年开发,并由Chet Ramey维护至今。Bash Shell是Bourne Shell(sh)的一个增强版本,提供了许多方便的特性,...
我们称其为“ BASH3 Boilerplate”或简称b3bp。 目标 删除键友好。 我们建议不要使用作为基础,而是删除包,包含文件,编译器等,并删除不需要的部分。 虽然起初可能有些陈旧,但这正是我们应该包含的Bash脚本的...
3. Bash的基本特性 Bash提供了许多基本特性,包括: * 命令历史记录:Bash提供了一个命令历史记录功能,用户可以查看和重复之前输入的命令。 * 命令补全:Bash提供了命令补全功能,用户可以输入部分命令,然后按下...
支持2021.3.22f1或更高 Meow Bash 3D 是一款有趣的 3D 游戏,玩家在其中控制凌乱房屋中的流浪猫。您的任务是穿越不同的级别,粉碎家居用品以赚取积分并收集硬币。与四只人工智能控制的猫竞争,每只猫都争夺在规定的...
"bash 官方手册/Bash Reference Manual" bash 官方手册是 GNU 项目的一部分,由 Chet Ramey 和 Brian Fox 编写,提供了 Bash shell 的详细参考手册。该手册涵盖了 Bash shell 的所有方面,包括基本语法、shell ...
在这个场景中,我们关注的是与Bash shell相关的配置文件——"bash.acp"和"bash.stx",这些文件是专门为EditPlus定制的,目的是增强在编辑Bash脚本时的用户体验。 `bash.acp` 文件是EditPlus的语法规则配置文件,...
commacd一种更快的移动方式(Bash 3+)。 在浏览“用法”部分时,请记住commacd不是autojump / z / fasd的替代品,也不是互斥的。 它不会跟踪历史记录,不会写任何命令更快地移动(Bash 3 + / Zsh)。 commacd不是...
3. **准备安装环境**:确认系统满足安装需求,比如足够的磁盘空间、必要的权限以及当前用户是root,因为系统级别的软件通常需要管理员权限来安装。 4. **安装包**:使用AIX的安装工具,如`installp`,来执行安装...
3. 运行`make`命令进行编译: ``` make ``` 4. 为确保没有编译错误,可以运行测试套件: ``` make check ``` 5. 如果测试成功,使用管理员权限安装Bash: ``` sudo make install ``` 6. 更新系统默认的...
3. **CygWin**:CygWin是一个开源项目,提供了一套工具,使得开发者可以在Windows上编译和运行Unix-like应用程序。然而,CygWin需要较大的安装体积,且可能带来额外的性能开销。 4. **UnxUtils**:UnxUtils是...
3. **条件测试与文件操作**:掌握测试表达式,用于检查文件类型、文件存在性、数值比较等,并学习如何进行文件的创建、移动、复制和删除。 4. **数组与关联数组**:Bash从4.0版本开始支持关联数组,这允许我们存储...
Bash 使用文档 bash 是一种广泛使用的 shellcript 语言,主要应用于 Linux 操作系统中。下面是 bash 的一些重要知识点: 一、什么是 shell shell 是 Linux 系统中,用户和内核之间的交互程序。它翻译用户输入的...
linux 服务器GNU Bash小于版本4.3有操作系统命令注入漏洞,需要对bash升级,下载解压 #tar zxvf bash-4.4.tar.gz #cd bash-4.4 #./configure (如果centos7编译失败,请先安装#yum install gcc) #make #make ...
**3. 输入/输出重定向** Bash支持将命令的标准输出(stdout)和标准错误(stderr)重定向到文件或另一个命令。例如,`>`用于覆盖文件内容,`>>`用于追加,`用于输入,`2>`和`2>&1`分别用于错误输出的重定向。 **4. ...
**Bash 概述** Bash,全称是 Bourne-Again SHell,是Linux操作系统中最常用的命令行解释器,也是Unix系统中的默认shell。它继承了早期的Bourne shell的功能,并添加了许多增强特性,使得它成为脚本编写和自动化任务...
3. Shell 扩展:Bash 支持多种扩展,如通配符扩展、波浪号扩展(`~`),用于快速定位用户的主目录、参数扩展、命令替换和算术扩展,极大地增强了脚本的功能和灵活性。 六、总结 Bash 参考手册为用户提供了全面的 ...
GitBash是一款在Windows操作系统上运行的命令行工具,它为用户提供了类Unix shell环境,以便于使用Git进行版本控制。GitBash包含了Git的所有功能,并且还包含了其他Unix工具,如bash shell、grep、sed、awk等,使得...
3. 网络工具:如网络连接检查、IP地址查询等脚本,方便进行网络诊断和维护。 4. 日志分析:通过解析和处理日志文件,提供有用的统计信息和警报。 5. 自动化任务:例如定时备份、定期清理无用文件的脚本,可以节省...