#!/bin/sh# 检查/设定临时目录 TMP=$ROOT/var/log/setup/tmp # removepkg 使用环境变量 ROOT 的值为根目录 # If the $TMP directory doesn't exist, create it: if [ ! -d $TMP ]; then rm -rf $TMP # make sure it's not a symlink or something stupid mkdir -p $TMP chmod 700 $TMP # no need to leave it open fi ADM_DIR=$ROOT/var/log PRES_DIR=$TMP/preserved_packages # 保留包内容所用到的目录# 函数: 显示 $1 目录下除了 $2 文件之外所有文件的内容 cat_except() { ( cd "$1" && cat `ls * | sed "/^$2\$/d"` ) }# 函数: 通过 doinst.sh 中的命令来得到符号链接的路径 # 这个函数不接受参数, 只是从标准输入读取输入, 处理后再输出 # # makepkg 制作软件包的时候, 可以选择把其中的符号链接删除, 并生成 doinst.sh, # 在 doinst.sh 中包含重新建立的命令, 下面是从 aaa_base-12.1.0-noarch-2.tgz.gz 中 # 抄出来的: # ( cd usr/man ; rm -rf cat1 ) # ( cd usr/man ; ln -sf /var/man/cat1 cat1 ) # 本函数通过分析第一行, 得到 usr/man/cat1 这样的符号链接 extract_links() { sed -n 's,^( *cd \([^ ;][^ ;]*\) *; *rm -rf \([^ )][^ )]*\) *) *$,\1/\2,p' }# 函数: 保留文件 # 当 PRESERVE 设置为 true 的时候, 把传进来的参数所代表的文件保存到 $PRES_DIR # 目录下面的 $PKGNAME 目录下, 并保持原先的目录结构 # $PKGNAME 是由 remove_packages 函数中得到的, 表示正在处理的包名称 preserve_file() { if [ "$PRESERVE" = "true" ]; then F="`basename "$1"`" D="`dirname "$1"`" if [ ! -d "$PRES_DIR/$PKGNAME/$D" ]; then mkdir -p "$PRES_DIR/$PKGNAME/$D" || return 1 fi cp -p "$ROOT/$D/$F" "$PRES_DIR/$PKGNAME/$D" || return 1 fi return 0 }# 函数: 保留目录 # 在 $PRES_DIR/$PKGNAME 下建立传入参数所代表的路径 # 使用的是 mkdir -p, 所以不一定要是一层目录 preserve_dir() { if [ "$PRESERVE" = "true" ]; then if [ ! -d "$PRES_DIR/$PKGNAME/$1" ]; then mkdir -p "$PRES_DIR/$PKGNAME/$1" || return 1 fi fi return 0 }# 函数: 从标准输入读入文件列表, 检查是否是文件后通过调用 preserve_file 来保存文件 # 如果列表中的某个文件实际是目录, 那么则使用 preserve_dir 来保留目录 # # 另, 此函数认为传进来的所有文件名称都是某个包中包含的, 所以如果有文件名从参数 # 传进来, 不是目录又不是文件, 则认为是其它软件包中包含的, 但不存在的文件, # 会给出相应的警告信息 keep_files() { while read FILE ; do if [ ! -d "$ROOT/$FILE" ]; then if [ -r "$ROOT/$FILE" ]; then echo " --> $ROOT/$FILE was found in another package. Skipping." preserve_file "$FILE" else if [ "`echo $FILE | cut -b1-8`" != "install/" ]; then echo "WARNING: Nonexistent $ROOT/$FILE was found in another package. Skipping." fi fi else preserve_dir "$FILE" fi done }# 函数: 类似 keep_files, 不过它认为标准输入得到的文件名要么是链接, 要么不存在 keep_links() { while read LINK ; do if [ -L "$ROOT/$LINK" ]; then echo " --> $ROOT/$LINK (symlink) was found in another package. Skipping." else echo "WARNING: Nonexistent $ROOT/$LINK (symlink) was found in another package. Skipping." fi done }# 函数: 删除文件 # 从标准输入中读取文件列表, 并依次删除其中文件 delete_files() { while read FILE ; do if [ ! -d "$ROOT/$FILE" ]; then if [ -r "$ROOT/$FILE" ]; then # 如果文件比安装记录要新, 则给出警告 if [ "$ROOT/$FILE" -nt "$ADM_DIR/packages/$PKGNAME" ]; then echo "WARNING: $ROOT/$FILE changed after package installation." fi if [ ! "$WARN" = "true" ]; then echo " --> Deleting $ROOT/$FILE" preserve_file "$FILE" && rm -f "$ROOT/$FILE" else # warn 模式, 只提示, 不实际删除 echo " --> $ROOT/$FILE would be deleted" preserve_file "$FILE" # preserve_file 会根据 PRESERVE 的设定来决定是否保留 fi else # 要删除的文件已经不存在了, 给出提示 echo " --> $ROOT/$FILE no longer exists. Skipping." fi else # 如果文件名实际是一个目录, 则调用 preserve_dir 来处理 preserve_dir "$FILE" fi done }# 函数: 删除链接, 类似 delete_files delete_links() { while read LINK ; do if [ -L "$ROOT/$LINK" ]; then if [ ! "$WARN" = "true" ]; then echo " --> Deleting symlink $ROOT/$LINK" rm -f $ROOT/$LINK else echo " --> $ROOT/$LINK (symlink) would be deleted" fi else echo " --> $ROOT/$LINK (symlink) no longer exists. Skipping." fi done }# 函数: 删除目录, 用来删除卸载软件之后的目录 # 在 remove_packages 函数中, 传递给它的是 uniq_list$$ 文件, 是被卸载软件独有的部分 delete_dirs() { # 逆序排序, 这样内层的目录就会排在前面 sort -r | \ while read DIR ; do if [ -d "$ROOT/$DIR" ]; then if [ ! "$WARN" = "true" ]; then # 目录为空 if [ `ls -a "$ROOT/$DIR" | wc -l` -eq 2 ]; then echo " --> Deleting empty directory $ROOT/$DIR" rmdir "$ROOT/$DIR" else echo "WARNING: Unique directory $ROOT/$DIR contains new files" fi else # warn 模式, 只提示, 不删除 echo " --> $ROOT/$DIR (dir) would be deleted if empty" fi fi done }# 这个函数从代码看, 是将 man* 下面的文件对应的 cat* 下的文件删除 # 但是我的机器上没看到 cat* 下有文件 delete_cats() { sed -n 's,/man\(./[^/]*$\),/cat\1,p' | \ while read FILE ; do if [ -f "$ROOT/$FILE" ]; then if [ ! "$WARN" = "true" ]; then echo " --> Deleting $ROOT/$FILE (fmt man page)" rm -f $ROOT/$FILE else echo " --> $ROOT/$FILE (fmt man page) would be deleted" fi fi done }# 获取软件名, 与 installpkg 中的相同 package_name() { STRING=`basename $1 .tgz` # Check for old style package name with one segment: if [ "`echo $STRING | cut -f 1 -d -`" = "`echo $STRING | cut -f 2 -d -`" ]; then echo $STRING else # has more than one dash delimited segment # Count number of segments: INDEX=1 while [ ! "`echo $STRING | cut -f $INDEX -d -`" = "" ]; do INDEX=`expr $INDEX + 1` done INDEX=`expr $INDEX - 1` # don't include the null value # If we don't have four segments, return the old-style (or out of spec) package name: if [ "$INDEX" = "2" -o "$INDEX" = "3" ]; then echo $STRING else # we have four or more segments, so we'll consider this a new-style name: NAME=`expr $INDEX - 3` NAME="`echo $STRING | cut -f 1-$NAME -d -`" echo $NAME # cruft for later ;) #VER=`expr $INDEX - 2` #VER="`echo $STRING | cut -f $VER -d -`" #ARCH=`expr $INDEX - 1` #ARCH="`echo $STRING | cut -f $ARCH -d -`" #BUILD="`echo $STRING | cut -f $INDEX -d -`" fi fi }# 函数: 把参数作为软件列表, 一个一个删除 remove_packages() { # 一个一个处理 for PKGLIST in $* do # 如果软件名包括了 .tgz, 则把 .tgz 去掉 PKGNAME=`basename $PKGLIST .tgz` echo # 如果没找到这个软件, 那么这里试着把这个软件作为短名称(像 # bash-3.1.017-i486-2 中的 bash 那样)来寻找长的包名称(name-version-arch-build) # 正常情况下, 一个短名称只会找到一个长名称, 但是这个不是强制性的, 如果有多个 # 对应的长名称, 那么只删出其中的一个, 如果要全部删除, 则需要再使用几次 removepkg if [ ! -e $ADM_DIR/packages/$PKGNAME ]; then SHORT="`package_name $PKGNAME`" # 这句没什么用 for long_package in $ADM_DIR/packages/${PKGNAME}* ; do if [ "`package_name $PKGNAME`" = "`package_name $long_package`" ]; then PKGNAME="`basename $long_package`" fi done fi if [ -r $ADM_DIR/packages/$PKGNAME ]; then if [ ! "$WARN" = true ]; then echo "Removing package $ADM_DIR/packages/$PKGNAME..." fi # 在包记录文件($ROOT/var/log/packages/$PKGNAME)中从 ./ 开始, 之后的行是这个包 # 所安装的文件及目录, 如果没有找到 ./, 那么就是从 FILE LIST: 开始 if fgrep "./" $ADM_DIR/packages/$PKGNAME 1> /dev/null 2>&1; then TRIGGER="^\.\/" else TRIGGER="FILE LIST:" fi if [ ! "$WARN" = true ]; then echo "Removing files:" fi # 将包记录文件中的文件/目录列表提取出来, 如果是以 FILE LIST: 开始的, # 则去掉这一行, 将结果排序后放入 $TMP/delete_list$$ # 之所以要排序, 是因为这个函数使用了 comm 命令来查找两个文件之间的相同和不同部分 # comm 要求两个文件都是排序的 # $$ 在脚本中表示本脚本的进程号, 在这个地方用来建立和其它程序不冲突的临时文件 # # comm 比较两个文件, 并输出三列, # 第一列显示的是只属于第一个文件的内容 # 第二列显示的是只属于第二个文件的内容 # 第三列显示的是两个文件共有的内容 # comm 有三个参数 -1, -2, -3 分别代表不显示第一列, 不显示第二列, 不显示第三列 # 所以 comm -12 就表示只显示第三列, 也就是显示出两个文件相同的部分 # comm -23 表示只显示第一列, 就是显示出只属于第一个文件的部分 sed -n "/$TRIGGER/,/^$/p" < $ADM_DIR/packages/$PKGNAME | \ fgrep -v "FILE LIST:" | sort -u > $TMP/delete_list$$ # 将除了被卸载的包之外其它包的记录排序后存放到一个文件($TMP/required_list$$)中 # 用以找出被卸载的包中包含在其它包中的文件 cat_except $ADM_DIR/packages $PKGNAME | sort -u > $TMP/required_list$$ # $ADM_DIR/scripts 目录下存放是软件包安装时的 doinst.sh 脚本, 如果存在, # 则从其中提取出符号链接列表, 排序并去除重复后存入 $TMP/del_link_list$$ 中 if [ -r $ADM_DIR/scripts/$PKGNAME ]; then extract_links < $ADM_DIR/scripts/$PKGNAME | sort -u > $TMP/del_link_list$$ # 同样将要除了被卸载的包的脚本之外其它脚本中的语句通过 extract_links 提取出 # 符号链接列表, 排序并去除重复后存放入 $TMP/required_links$$ cat_except $ADM_DIR/scripts $PKGNAME | extract_links | \ sort -u > $TMP/required_links$$ # 将 required_list$$ required_links$$ 的内容合并, 排序, 并去除重复项, # 存入 $TMP/required_list$$ mv $TMP/required_list$$ $TMP/required_files$$ sort -u $TMP/required_links$$ $TMP/required_files$$ > $TMP/required_list$$ # 将被卸载的包中存在于其它包内的文件/链接交给 keep_links 处理 # 因为它寻找的是被卸载的包中的链接, 而在其它包中, 同名的文件也许就不再是链接 # 而是一个常规文件了, 所以上面要把 required_list$$ 和 required_links$$ 合并 # 而这个地方要交给 keep_links, delete_links 而不是 keep_files,delete_files 来处理 comm -12 $TMP/del_link_list$$ $TMP/required_list$$ | keep_links # 将被卸载的删除的包中不存在于其它包内的文件/链接交给 delete_links 处理 comm -23 $TMP/del_link_list$$ $TMP/required_list$$ | delete_links else # 没有 $ADM_DIR/scripts/$PKGNAME 文件 # 与上面类似, 不过是在没有 $ADM_DIR/scripts/$PKGNAME 文件的情况下, # 所以不需要用 cat_except, 而是直接用 cat # 而且没有 $ADM_DIR/scripts/$PKGNAME 就表示不会从 doinst.sh 中处理出符号链接来 # 所以不需要处理符号链接 cat $ADM_DIR/scripts/* | extract_links | \ sort -u > $TMP/required_links$$ mv $TMP/required_list$$ $TMP/required_files$$ sort -u $TMP/required_links$$ $TMP/required_files$$ >$TMP/required_list$$ fi # 对普通文件/目录, 用 keep_files 来处理 comm -12 $TMP/delete_list$$ $TMP/required_list$$ | keep_files # 对于只存在于被卸载的包中的文件/目录, 调用 delete_file, delete_dir, delete_cats # 来进行处理, 不明白为什么要调用 delete_cats, 难道 delete_files 和 delete_dirs # 不能把所有的文件都删除 comm -23 $TMP/delete_list$$ $TMP/required_list$$ > $TMP/uniq_list$$ delete_files < $TMP/uniq_list$$ delete_dirs < $TMP/uniq_list$$ delete_cats < $TMP/uniq_list$$ # KEEP 不为 true 的时候, 把 $TMP 下的那些文件列表删除 # 这些列表分别是: # 被卸载软件的文件列表, 符号链接列表 # 其它软件的文件列表, 符号链接列表 # 被卸载软件独有的文件/符号链接列表 # 其它软件的文件/符号链接列表 if [ ! "$KEEP" = "true" ]; then rm -f $TMP/delete_list$$ $TMP/required_files$$ $TMP/uniq_list$$ rm -f $TMP/del_link_list$$ $TMP/required_links$$ $TMP/required_list$$ fi # 如果设置了 PRESERVE 为 true, 那么把 $ADM_DIR/scripts/$PKGNAME 脚本重新以 # install/doinst.sh 的名字存放到 $PRES_DIR/$PKGNAME 下 if [ "$PRESERVE" = "true" ]; then if [ -r $ADM_DIR/scripts/$PKGNAME ]; then if [ ! -d "$PRES_DIR/$PKGNAME/install" ]; then mkdir -p "$PRES_DIR/$PKGNAME/install" fi cp -p $ADM_DIR/scripts/$PKGNAME $PRES_DIR/$PKGNAME/install/doinst.sh fi fi # 将记录文件存放到 $ADM_DIR/removed_packages 目录下 # 将脚本文件存放到 $ADM_DIR/removed_scripts 目录下 if [ ! "$WARN" = "true" ]; then for DIR in $ADM_DIR/removed_packages $ADM_DIR/removed_scripts ; do if [ ! -d $DIR ] ; then mkdir -p $DIR ; chmod 755 $DIR ; fi done mv $ADM_DIR/packages/$PKGNAME $ADM_DIR/removed_packages if [ -r $ADM_DIR/scripts/$PKGNAME ]; then mv $ADM_DIR/scripts/$PKGNAME $ADM_DIR/removed_scripts fi fi else echo "No such package: $ADM_DIR/packages/$PKGNAME. Can't remove." fi done }# 无参数, 则显示用法并退出程序 if [ "$#" = "0" ]; then echo "Usage: `basename $0` [-copy] [-keep] [-preserve] [-warn] packagename ..."; exit 1 fi# 死循环, 用来处理参数 # while : ; do 中的 : 是一个 sh 内置命令: 空命令, 它什么都不干, 只是返回 0 while : ; do case "$1" in -copy) WARN=true; PRESERVE=true; shift;; # 只是把文件保存到保留目录下, 不删除 -keep) KEEP=true; shift;; # KEEP 为 true 时, 会保留 $TMP 下的各种文件列表文件 -preserve) PRESERVE=true; shift;; # 保存包内容并删除软件 -warn) WARN=true; shift;; # 只显示相关信息, 不真正删除 # 非上面四种以之外任何以 - 开头的参数都是非法的, 显示用法并退出 -*) echo "Usage: `basename $0` [-copy] [-keep] [-preserve] [-warn] packagename ..."; exit 1;; # 遇到第一个非 - 开头的参数, 或者是不再有参数时则退出参数处理, 剩下的部分都认为是软件 *) break esac done# 根据 WARN 和 PRESERVE 的设定情况来显示相关的信息 if [ "$WARN" = "true" ]; then echo "Only warning... not actually removing any files." if [ "$PRESERVE" = "true" ]; then echo "Package contents is copied to $PRES_DIR." fi echo "Here's what would be removed (and left behind) if you" echo "removed the package(s):" echo else if [ "$PRESERVE" = "true" ]; then echo "Package contents is copied to $PRES_DIR." fi fi# 调用 removed_packages 来处理剩下的参数 # 剩下的参数是由参数处理部分处理后的部分, 它们被认为是软件列表 remove_packages $*
slackware 包管理(pkgtools)分析 removepkg
2011-02-03 15:16