LFS包管理与Shell脚本

 Linux  Shell  包管理  LFS 󰈭 7312字

参考文档

More Control and Package Management using Package Users (v1.4)

工具包: more_control_helpers

Shell 编程

特殊变量

$开头的一些特殊变量:

Text
 1Special Parameters
 2    The shell treats several parameters specially.  These parameters may only
 3    be referenced; assignment to them is not allowed.
 4    *      Expands  to the positional parameters, starting from one.  When the
 5    expansion is not within double quotes, each positional parameter expands to
 6    a separate word.  In contexts where it is performed, those words are
 7    subject to further word splitting and pathname expansion.  When the
 8    expansion occurs within double quotes, it expands to a single word with the
 9    value of each parameter separated by the first character  of  the  IFS
10    special  variable. That  is,  "$*"  is equivalent to "$1c$2c...", where c
11    is the first character of the value of the IFS variable. If IFS is unset,
12    the parameters are separated by spaces.  If IFS is null, the parameters are
13    joined without in tervening separators.
14    @      Expands  to  the positional parameters, starting from one.  In
15    contexts where word splitting is performed, this expands each positional
16    parameter to a separate word; if not within double quotes, these words are
17    subject  to word splitting.  In contexts where word splitting is not
18    performed, this expands to a single word with each po sitional parameter
19    separated by a space.  When the expansion occurs within double quotes,
20    each  parameter  ex pands  to a separate word.  That is, "$@" is
21    equivalent to "$1" "$2" ...  If the double-quoted expansion occurs within a
22    word, the expansion of the first parameter is joined with the beginning
23    part of the original word, and the expansion of the last parameter is
24    joined with the last part of the original word.  When there are no posi
25    tional parameters, "$@" and $@ expand to nothing (i.e., they are removed).
26    #      Expands to the number of positional parameters in decimal.
27    ?      Expands to the exit status of the most recently executed foreground
28    pipeline.
29    -      Expands to the current option flags as specified upon invocation, by
30    the set builtin command, or those  set  by the shell itself (such as the -i
31    option).
32    $      Expands  to  the process ID of the shell.  In a () subshell, it
33    expands to the process ID of the current shell, not the subshell.
34    !      Expands to the process ID of the job most recently placed into the
35    background, whether executed as an asynchronous command or using the bg
36    builtin (see JOB CONTROL below).
37    0      Expands  to  the  name  of the shell or shell script.  This is set
38    at shell initialization.  If bash is invoked with a file of commands, $0 is
39    set to the name of that file.  If bash is started with the -c option, then
40    $0 is set  to  the  first  argument  after the string to be executed, if
41    one is present.  Otherwise, it is set to the filename used to invoke bash,
42    as given by argument zero.

type命令

默认的man好像显示的不是bash command的type, 因而去别处找了一份mannual:

Text
 1NAME
 2    type - Display information about command type.
 3
 4SYNOPSIS
 5    type [-afptP] name [name ...]
 6
 7DESCRIPTION
 8    Display information about command type.
 9    
10    For each NAME, indicate how it would be interpreted if used as a
11    command name.
12    
13    Options:
14      -a	display all locations containing an executable named NAME;
15    		includes aliases, builtins, and functions, if and only if
16    		the `-p' option is not also used
17      -f	suppress shell function lookup
18      -P	force a PATH search for each NAME, even if it is an alias,
19    		builtin, or function, and returns the name of the disk file
20    		that would be executed
21      -p	returns either the name of the disk file that would be executed,
22    		or nothing if `type -t NAME' would not return `file'
23      -t	output a single word which is one of `alias', `keyword',
24    		`function', `builtin', `file' or `', if NAME is an alias,
25    		shell reserved word, shell function, shell builtin, disk file,
26    		or not found, respectively
27    
28    Arguments:
29      NAME	Command name to be interpreted.
30    
31    Exit Status:
32    Returns success if all of the NAMEs are found; fails if any are not found.
33
34SEE ALSO
35    bash(1)
36
37IMPLEMENTATION
38    GNU bash, version 5.0.17(1)-release (x86_64-redhat-linux-gnu)
39    Copyright (C) 2019 Free Software Foundation, Inc.
40    License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

其余指令

man 即可.

工具包

既然决定使用package user的办法管理所有的包了, 自然也要自己审查、学习一下大牛的shell代码.

/sbin/add_package_user

这个脚本会帮助我们为新的package创建user,分配group并根据需要创建home目录.

首先是一些提示信息以及基本的参数读取方式:

bash
 1#Package user home directories will be located under this directory
 2homebase=/usr/src
 3
 4#Contents of following directory are copied into home directory when creating 
 5#a new package user (existing files will not be overwritten)
 6skel=/etc/pkgusr/skel-package
 7
 8if [ $# -lt 7 ]; then
 9  echo 1>&2 'USAGE: '
10  echo 1>&2 'add_package_user <description> <name> <minuid> <maxuid> \'
11  echo 1>&2 '                              <group> <mingid> <maxgid> [-d <home>]'
12  echo 1>&2
13  echo 1>&2 'If a user account called <name> exists, a message will be printed and'
14  echo 1>&2 'everything will be left as-is. If a user account called <name> does not'
15  echo 1>&2 'exist, one will be created.'
16  echo 1>&2 'The account'"'"'s primary group will be <group> and the /etc/passwd'
17  echo 1>&2 'description field will be set to <description>. If a group called <group>'
18  echo 1>&2 'does not already exist, one will be created.'
19  echo 1>&2 'The new account will get the "install" group as a supplementary group. If'
20  echo 1>&2 'a group named "install" does not exist, one will be created.'
21  echo 1>&2
22  echo 1>&2 '<description> needs to be a valid string for the /etc/passwd description'
23  echo 1>&2 '  field. This means, among other things, that it must not contain ":".'
24  echo 1>&2 '  Don'"'"'t forget to properly quote <description> if it contains spaces or'
25  echo 1>&2 '  other characters interpreted by the shell!'
26  echo 1>&2
27  echo 1>&2 '<minuid>(incl.) and <maxuid>(excl.) determine the numeric range from which'
28  echo 1>&2 '  the new account'"'"'s UID will be picked in the following way:'
29  echo 1>&2
30  echo 1>&2 '  1. If the range contains no unused UID => Exit with error.'
31  echo 1>&2 '  2. If <maxuid>-1 is still unused, find the greatest UID from the range'
32  echo 1>&2 '     that is used and pick the number after that.'
33  echo 1>&2 '  3. If <maxuid>-1 is in use, pick the first unused number from the range.'
34  echo 1>&2
35  echo 1>&2 '<mingid>(incl.) and <maxgid>(excl.) determine the numeric range from which'
36  echo 1>&2 '  to pick the GID for group <group> and/or group "install", if it needs to be'
37  echo 1>&2 '  created. The process for picking the GID is the same as that for the UID.'
38  echo 1>&2 ''
39  echo 1>&2 '<home> specifies the new user'"'"'s home directory. If it is not provided,'
40  echo 1>&2 '  it will default to '"$homebase/<name> ."
41  echo 1>&2 '  If the home directory does not exist yet it will be created, otherwise'
42  echo 1>&2 '  the existing directory will be recursively chown'"'"'ed to the new user.'
43  echo 1>&2 '  The home directory will be populated with a copy of the contents of'
44  echo 1>&2 "  $skel, but pre-existing files in the home directory"
45  echo 1>&2 '  will not be overwritten. Note that symlinks will be copied as symlinks!'
46  echo 1>&2 ''
47  exit 1
48fi
49
50grpfile=/etc/group
51passwd=/etc/passwd
52
53description=$1
54name=$2
55minuid=$3
56maxuid=$4
57gname=$5
58mingid=$6
59maxgid=$7

随后比较精妙, 通过set -- "$@" xxx的办法相当于构造了一个循环队列, 通过添加终结符先处理-d <home>的参数, 而能保持其余的参数不受到影响.

bash
 1home=$homebase/$name
 2
 3set -- "$@" _eNd_OF_lisT_
 4while [ "$1" != "_eNd_OF_lisT_" ]; do
 5  case "$1" in
 6    -d) shift 1
 7        if [ "$1" = "_eNd_OF_lisT_" ]; then
 8          echo 1>&2 "-d directory name missing!"
 9          exit 1
10        fi
11        home="$1"
12        shift 1
13        ;;
14    *) temp="$1" 
15       shift 1
16       set -- "$@" "$temp"
17       ;;
18  esac     
19done
20shift 1 #throw away _eNd_OF_lisT_

检查$UID是否为0来判断是否为root, 并查询passwd看该包用户是否已经存在.

bash
1if [ $UID -ne 0 ]; then echo Please run this script as root. ; exit 1; fi
2
3#test if user already exists
4grep "^$name:.*" $passwd
5if [ $? -eq 0 ]; then 
6  echo 'Package user does already exist! Do su '$name' to do maintenance work.'
7  exit 1
8fi 

下面处理不存在的情况, 先检验参数类型, expr默认好像只能接受整数类型, 以此来检验参数类型, 值得学习.

布尔短路的判断也很妙, expr出错无法执行后即执行error=1.

随后的cut指令-d指定分隔符, -f指定提取第几列(从1开始), sort的-n表示按照数字排序.

然后就是uid查找算法, 没有语法上的困难, 就不赘述了.

bash
 1error=0
 2expr ${minuid} + 1 2>/dev/null 1>&2 || error=1
 3expr ${maxuid} + 1 2>/dev/null 1>&2 || error=1
 4expr ${mingid} + 1 2>/dev/null 1>&2 || error=1
 5expr ${maxgid} + 1 2>/dev/null 1>&2 || error=1
 6
 7if [ $error -eq 1 ]; then
 8  echo Error: Illegal numeric value!
 9  exit 1
10fi
11
12if [ $minuid -ge $maxuid ]; then
13  echo 'Error: minuid must be less than maxuid !' 
14  exit 1
15fi
16
17if [ $mingid -ge $maxgid ]; then
18  echo 'Error: mingid must be less than maxgid !' 
19  exit 1
20fi
21
22uidlist=`cut -d : -f 3 $passwd | sort -n`
23
24#find last used UID within range
25u=0
26for i in $uidlist
27do
28  if [ $i -ge $maxuid ]; then break; fi
29  if [ $i -ge $minuid ]; then u=$i; fi 
30done
31
32#if no UID from the range is used, pick the first, otherwise pick the one
33#immediately following the last UID in use.
34if [ $u -eq 0 ]; then u=$minuid; else u=`expr $u + 1`; fi
35
36#if the UID determined above is >= maxuid (i.e. illegal)
37#then we look for the first unused uid in the range.
38if [ $u -ge $maxuid ]; then
39  u=$minuid
40  for i in $uidlist
41  do
42    if [ $u -eq $i ]; then u=`expr $u + 1` ; fi
43    if [ $i -ge $maxuid ]; then break; fi
44  done  
45
46  if [ $u -ge $maxuid ]; then
47    echo Error: UID range is full!
48    exit 1
49  fi
50fi
51
52echo Will create user $name with uid: $u
53
54unset uidlist

group的分配也类似, createinstall和oldg指示install组, 而creategroup和g指示新包对应的组.. 代码观感一言难尽不过…

bash
 1#execute the following for gname and "install" to get gids for those 2 groups
 2
 3g=0
 4creategroup=0
 5for group in install $gname
 6do
 7  oldg=$g #save gid from previous run
 8  createinstall=$creategroup
 9  creategroup=0
10 
11  #test if group already exists and extract gid if so
12  g=`grep ^${group}:.\* $grpfile | cut -d : -f 3 -`
13
14  #if group does not exist, then check range for a free gid
15  if [ z$g = z ]; then 
16    creategroup=1
17    
18    gidlist=`cut -d : -f 3 $grpfile | sort -n`
19
20    #find last used GID within range
21    g=0
22    for i in $gidlist
23    do
24      if [ $i -ge $maxgid ]; then break; fi
25      if [ $i -ge $mingid ]; then g=$i; fi
26    done
27
28    #if no GID from the range is used, pick the first, otherwise pick the one
29    #immediately following the last GID in use.
30    if [ $g -eq 0 ]; then g=$mingid; else g=`expr $g + 1`; fi
31    
32    #don't reuse gid from previous run 
33    if [ $g -eq $oldg ]; then g=`expr $g + 1`; fi
34
35    #if the GID determined above is >= maxgid (i.e. illegal)
36    #then we look for the first unused gid in the range.
37    if [ $g -ge $maxgid ]; then
38      g=$mingid
39      for i in $gidlist
40      do
41        if [ $g -eq $i ]; then g=`expr $g + 1` ; fi
42        if [ $g -eq $oldg ]; then g=`expr $g + 1` ; fi
43        if [ $i -ge $maxgid ]; then break; fi
44      done  
45
46      if [ $g -ge $maxgid ]; then
47        echo Error: GID range is full!
48        exit 1
49      fi
50    fi
51  fi
52done
53
54unset gidlist

最后将group和user实装上去, 并使用骨架初始化其home文件夹.

需要注意L21中:

  • yes n 不断输出’n’这个字符, 而且yes的效率其实非常高, 可以后续研究一下 讲个小故事:Unix的yes命令

  • cp的几个参数说明:

    • -a: same as -dR –preserve=all
    • -R: copy directories recursively
    • -d: same as –no-dereference –preserve=links
    • –no-dereference: never follow symbolic links in SOURCE 不加这个好像就会把连接的目标也拷贝一次过来
    • -i: prompt before overwrite 结合yes n采用默认拒绝的方案, 十分的安全
  • 正则{[^.],.[^.],..?}* 没太搞懂, 留坑.

bash
 1if [ $createinstall -eq 1 ]; then
 2  echo Creating group install with gid $oldg
 3  groupadd -g $oldg install || exit 1
 4else
 5  echo Group install has gid $oldg
 6fi
 7if [ $creategroup -eq 1 ]; then
 8  echo Creating group $gname with gid $g
 9  groupadd -g $g $gname || exit 1
10else 
11  echo Group $gname has gid $g
12fi
13
14useradd -c "${description}" -d ${home} -g ${gname} -G install \
15        -s /bin/bash -u ${u} ${name}  || exit 1
16
17mkdir -p $home || exit 1
18
19yes n|cp -ai -R ${skel}/{[^.],.[^.],..?}* ${home} 2>/dev/null >/dev/null
20
21cd ${home}
22chown --recursive ${u}:${g} .
23
24exit 0

/sbin/install_package

这个脚本就比较简单了, 进一步封装了add_packge_user的功能.

bash
 1if [ $# -eq 1 ]; then
 2  set -- "$1" "$1" "$1"
 3fi
 4
 5if [ $# -ne 3 ]; then
 6  echo 1>&2
 7  echo 1>&2 'USAGE 1: install_package <description> <name> <group>'
 8  echo 1>&2 'USAGE 2: install_package <name>'
 9  echo 1>&2
10  echo 1>&2 'Creates a new package user called <name> with primary group <group>'
11  echo 1>&2 'and description <description>.'
12  echo 1>&2 'If the user account has been created successfully, `su - <name>'"'"' will be'
13  echo 1>&2 'executed so that you can start working with the new account right away.'
14  echo 1>&2
15  echo 1>&2 '<description> needs to be a valid string for the /etc/passwd description'
16  echo 1>&2 '  field. This means, among other things, that it must not contain ":".'
17  echo 1>&2 '  Don'"'"'t forget to properly quote <description> if it contains spaces or'
18  echo 1>&2 '  other characters interpreted by the shell!'
19  echo 1>&2
20  echo 1>&2 'This script leaves the actual creation of the new account to the'
21  echo 1>&2 'add_package_user script. Check out its documentation for details.'
22  echo 1>&2
23  echo 1>&2 'When called with just one argument, that argument will be used as'
24  echo 1>&2 '<name>, <description> and <group>'
25  exit 1
26fi
27
28if [ $UID -ne 0 ]; then echo Please run this script as root. ; exit 1; fi
29add_package_user "${1}" $2 10000 20000 $3 10000 20000 || exit 1
30su - $2

/bin/forall_direntries_from

该工具包主要可以用于包管理, 类似find的功能.

在help中可以复习一下###删除字符串的功能, 可以参考 鸟哥的 Linux 私房菜 – 学习 bash shell 变量内容的删除、取代与替换, #是从左向右最短匹配删除, ##是最长匹配; 因而L25提取了不包含可能存在的路径的纯文件名.

bash
 1#The following list should contain the mount points of all filesystems
 2#that are to be scanned as a space-separated list within parentheses. 
 3#/ will usually be in this list and if you have /usr
 4#on a separate partition, it will also be in this list. Other non-special
 5#filesystems where package users could own files should also be put in this
 6#list.
 7#Mount points whose filesystems are special, such as procfs or sysfs must
 8#not be in this list. While a simple find on those special filesystems should 
 9#be harmless, operations such as "-exec grep something" are NOT SAFE and may 
10#have HARMFUL SIDE-EFFECTS, especially when performed as root. 
11fs_to_scan=(/)
12
13#Files with a path prefix found in the following list are ignored.
14#This list will usually contain the parent directory of your package users'
15#home directories, because normally you don't want to scan those. You can
16#also add other directories that will never contain package user files, such
17#as /home. This reduces scan time.
18#NOTE: The LFS-6.0 book uses a ramfs mounted on /dev and with that setup
19#/dev does not need to be in the prune list. But since there is no requirement
20#that /dev have its on filesystem it's better to prune it explicitly.
21prune_prefixes=(/home /usr/src /dev /tools) #NO TRAILING SLASHES!!!!
22
23if [ $# -lt 1 -o "$1" = "--help" ]; then
24  echo 1>&2
25  echo 1>&2 'USAGE: '"${0##*/}"' <user_or_group_name> [<find-commands>]'
26  echo 1>&2
27  echo 1>&2 '  If <find-commands> contains no action other than -prune, -print will be'
28  echo 1>&2 '    executed for all matching files.'
29  echo 1>&2 '  Entries will be matched if group and/or user equals <user_or_group_name>'
30  echo 1>&2 '    (numeric UID/GID allowed).'
31  echo 1>&2 '  All matching entries will be acted on, including device special files, so'
32  echo 1>&2 '    you should be extra careful with the <find-commands> you provide!'
33  echo 1>&2
34  exit 1
35fi

关于这一点网上没有找到有关资料, 问了下chatgpt, 非常的清楚:

In Bash shell, the command trap ‘:’ SIGPIPE sets up a signal trap for the SIGPIPE signal, instructing the shell to take no action when this signal is received.

SIGPIPE is a signal that is typically generated when a process tries to write to a pipe or socket that has been closed or has no readers. By default, when a SIGPIPE signal is generated, the process that triggered it will be terminated.

In Bash shell, trap is a command that allows you to specify how the shell should handle various signals. The syntax of the trap command is as follows:

trap COMMAND SIGNAL

Here, COMMAND specifies the action to be taken when the specified SIGNAL is received. In the case of trap ‘:’ SIGPIPE, the command : is used to specify that no action should be taken when a SIGPIPE signal is received.

So, when you run trap ‘:’ SIGPIPE, it sets up a signal trap that will prevent the shell from terminating if it receives a SIGPIPE signal. Instead, the shell will simply ignore the signal and continue executing the commands in the script or shell session.

bash
1#suppress ugly debug output from shell
2trap ':' SIGPIPE

再继续看之前, 学习一下bash的数组展开.

Bash数组展开是一种Bash shell功能,它可以将数组解释为一组单独的元素,以便在脚本中使用。展开可以通过许多方式进行,其中最常用的是${array[@]}和${array[*]}语法。这两种语法将数组array中的每个元素解释为单独的参数,以便在Bash脚本中使用。

${array[@]}语法将数组解释为由多个单独元素组成的列表。这些元素由空格分隔。

${array[*]}语法将数组解释为一个字符串,其中元素由特定的分隔符分隔(默认情况下,分隔符为空格)。

比如array=("hello world" "foo bar") , 则${array[@]}会生成"hello world" "foo bar", 而${array[*]}会生成"hello world foo bar".

而在array前加个#则会统计数组长度.

那么再看到下面的代码:

bash
 1ugname="$1"
 2shift 1  #remove user_or_group_name from argument list
 3
 4# Recent versions of find issue a warning if "-depth" is listed after a
 5# non-option argument. To prevent this warning if -depth is passed to
 6# this script, we pick up the "-depth" argument here to move it to the
 7# front later on.
 8depth=""
 9if [ "_$1" = "_-depth" ]; then
10  depth=-depth
11  shift 1
12fi
13
14ugmatcher=(-false)
15#test if find accepts ugname as a user, and append to ugmatcher if it does
16if find / -maxdepth 0 -user "$ugname" >/dev/null 2>&1 ; then
17  ugmatcher[${#ugmatcher[@]}]="-or"
18  ugmatcher[${#ugmatcher[@]}]="-user"
19  ugmatcher[${#ugmatcher[@]}]="$ugname"
20fi
21#test if find accepts ugname as a group, and append to ugmatcher if it does
22if find / -maxdepth 0 -group "$ugname" >/dev/null 2>&1 ; then
23  ugmatcher[${#ugmatcher[@]}]="-or"
24  ugmatcher[${#ugmatcher[@]}]="-group"
25  ugmatcher[${#ugmatcher[@]}]="$ugname"
26fi
27
28#if find accepted ugname as neither user nor group, then exit
29if [ "${#ugmatcher[@]}" = 1 ]; then
30  echo 1>&2 'find does not accept `'"$ugname'"' as group or user name'
31  exit 1
32fi
33
34#construct find commands that match the prune_prefixes. Each prefix will be
35#matched as -path <prefix> -or -path <prefix>/*
36#so that the directory itself and all subdirectories are matched.
37y=(\( -false)
38for ((i=0; $i<${#prune_prefixes[@]}; i=$i+1)) 
39do
40  y[${#y[@]}]='-or'
41  y[${#y[@]}]=-path
42  y[${#y[@]}]="${prune_prefixes[$i]}"
43  y[${#y[@]}]='-or'
44  y[${#y[@]}]=-path
45  y[${#y[@]}]="${prune_prefixes[$i]}/*"
46done
47y[${#y[@]}]=')'
48
49#In the following find command, the part
50# -not ( ( "${y[@]}" -prune ) -or "${y[@]}" )
51#is responsible for preventing the files that match prune_prefixes from
52#being processed. The 2nd "${y[@]}" may seem redundant, but it isn't, because
53#-prune has no effect and is always false when -depth is used.
54#The -true before "$@" ensures that -depth can be passed as only parameter.
55find "${fs_to_scan[@]}" $depth -xdev -noleaf \
56     -not \( \( "${y[@]}" -prune \) -or "${y[@]}" \) \
57     -and \( "${ugmatcher[@]}" \) -and \( -true "$@" \)

L14新建一个拥有1个元素的数组, 再在L17-19本质上是不断地append新元素进去.

L16-26这段代码通过-maxdepth=0限制了搜索的深度, 因而其实不要求其进行任何实际的搜索, 只是做一个合法性检查.

一个简单例子是:

bash
 1rqdmap in ~
 2[ 19:06:35 ]if find / -maxdepth 0 -user rqdmap; then echo OK; else echo fail; fi
 3OK
 4rqdmap in ~
 5[ 19:06:38 ]if find / -maxdepth 0 -user root; then echo OK; else echo fail; fi
 6/
 7OK
 8rqdmap in ~
 9[ 19:06:43 ]if find / -maxdepth 0 -user whoIsrqdmpa; then echo OK; else echo fail; fi
10find: ‘whoIsrqdmpa’ is not the name of a known user
11fail

L29-32处理了既不是合法的username又不是合法的groupname的情况, 报错后exit即可.

最后看到find的几个参数:

  • -depth: Process each directory’s contents before the directory itself. 有一点像二叉树的后序遍历

  • -xdev: Don’t descend directories on other filesystems. 遍历root的时候不会跑到其他分区.

  • -noleaf: 文档是这么说的:

    Do not optimize by assuming that directories contain 2 fewer subdirectories than their hard link count. This option is needed when searching filesystems that do not follow the Unix directory-link convention, such as CD-ROM or MS-DOS filesystems or AFS volume mount points. Each directory on a normal Unix filesystem has at least 2 hard links: its name and its .' entry. Additionally, its subdirectories (if any) each have a ..’ entry linked to that directory. When find is examining a directory, after it has statted 2 fewer subdirectories than the directory’s link count, it knows that the rest of the entries in the directory are non-directories (`leaf’ files in the directory tree). If only the files’ names need to be examined, there is no need to stat them; this gives a significant increase in search speed.

    • 读了半天好像也不太懂?… 大概意思可能是, 如果一个文件夹的hard links数量=子文件夹数量+2(这里具体可以参考 Linux文件权限), 那么该目录的其他入口就不是一个文件夹了, 言下之意是说其余的入口就是符号链接了? 那么find就会将其优化掉不再搜索一遍了, 提高了搜索速度. 通过-noleaf即可禁用该优化强制搜索所有的子目录.
  • -and, -not, -or等测试必须为全部真才能匹配文件.

    • 第一个not中用于剪枝, 为什么要有第二个-or "${y[@]}"的原因在于当-depth启用后-prune总为false, 可以参考man, 这样可以保证无论有没有-depth都能匹配上对应的文件夹, 结合-not成功剪枝.

    -prune True; if the file is a directory, do not descend into it. If -depth is given, then -prune has no effect.

    • 第二个通过-and匹配上对应的user/group name

    • 第三个通过-and以及-true来允许通过shell传递其他参数, 如-print, -exec等.

/bin/uninstall_package

安全的删除指令, 执行后并不会实际删除任何文件或文件夹, 而是再给出一段调用forall_direntries_from的指令, 复制并执行后也不会真正删除文件, 而是echo出将要删除的文件名, 等待用户进一步确认后才会动手, 非常的好.

bash
 1if [ $# != 1 -o "$1" = '--help' ]; then
 2  echo 1>&2 'USAGE: uninstall_package <package-name>'
 3  exit 1
 4fi
 5echo
 6echo '# If package '"$1"' has setuid root binaries, then you need to be'
 7echo '# root for the following to work.'
 8echo '# Otherwise, you can do it as user '"$1"'.'
 9echo
10echo 'forall_direntries_from "'"$1"'" -depth \( -type d -exec echo rmdir {} \; \) -or \( -not -type d -exec echo rm -f {} \; \)'
11echo
12echo '# After successfully deleting all files, you may want to remove the'
13echo '# package user '"$1"'. But remember that if you do that you need to'
14echo '# remove or change ownership of '"$(eval echo ~"$1")"'. Unless you are'
15echo '# certain that you will never re-install '"$1"', it is probably better to'
16echo '# just keep the package user '"$1"' and its home directory around.'
17echo '# Anyway, if you want to delete the account, you can use the following'
18echo '# command:'
19echo 
20echo 'userdel "'"$1"'"'
21echo
22echo '# If your /etc/login.defs has USERGROUPS_ENAB set to "yes" (the default),'
23echo '# then userdel will automatically delete the package user'"'"'s group if'
24echo '# its name is identical to the user name. Otherwise, if you want to delete'
25echo '# the package user'"'"'s group, you will need to use the `groupdel'"'"' command.'
26echo

/bin/list_suspicious_files

检查一下是否有可疑的软件包, 比如setuid和setgid的文件, 可以随意写的文件, 烂掉的软连接, 硬链接等.

bash
  1#!/bin/bash
  2
  3#The following list should contain the mount points of all filesystems
  4#that are to be scanned as a space-separated list within parentheses. 
  5#/ will usually be in this list and if you have /usr
  6#on a separate partition, it will also be in this list. Other non-special
  7#filesystems where suspicious files could be located should also be put in 
  8#this list.
  9#Mount points whose filesystems are special, such as procfs or sysfs should
 10#not be in this list. 
 11fs_to_scan=(/)
 12
 13#Files with a path prefix found in the following list are ignored.
 14#DO !!!!NOT!!! PUT /usr/src OR WHATEVER THE HOME DIRECTORY prefix is for your
 15#package users into this list!!! You DO want to scan those directories in
 16#order to spot e.g. world-writable tarballs and other abominations that
 17#may have crept in.
 18#Ideally, this list should be empty.
 19prune_prefixes=() #NO TRAILING SLASHES!!!
 20
 21#If the following variable is set to "yes", then files that contain
 22#control characters or other non-printable characters (except for space)
 23#will be reported as suspicious. 
 24#This test slows down the search considerably!
 25enable_illchars=yes
 26
 27
 28#suppress ugly debug output from shell
 29trap ':' SIGPIPE
 30
 31#"-false" as 1st argument is used when called by list_suspicious_files_from
 32if [ $# -ge 1 -a "$1" != "-false" ]; then
 33  echo 1>&2 
 34  echo 1>&2 "USAGE: ${0##*/}"
 35  echo 1>&2 
 36  echo 1>&2 '  Outputs a categorized list of files and directories with properties'
 37  echo 1>&2 '  that could mean trouble and should be investigated.'
 38  echo 1>&2
 39  exit 1
 40fi
 41
 42
 43usergroupmatch=(-true)
 44if [ "$1" = "-false" ]; then
 45  usergroupmatch=(\( "$@" \))
 46fi
 47
 48#construct find commands that match the prune_prefixes. Each prefix will be
 49#matched as -path <prefix> -or -path <prefix>/*
 50#so that the directory itself and all subdirectories are matched.
 51y=(\( -false)
 52for ((i=0; $i<${#prune_prefixes[@]}; i=$i+1)) 
 53do
 54  y[${#y[@]}]='-or'
 55  y[${#y[@]}]=-path
 56  y[${#y[@]}]="${prune_prefixes[$i]}"
 57  y[${#y[@]}]='-or'
 58  y[${#y[@]}]=-path
 59  y[${#y[@]}]="${prune_prefixes[$i]}/*"
 60done
 61y[${#y[@]}]=')'
 62
 63illchars=( $'\x1'  $'\x2'  $'\x3'  $'\x4'  $'\x5'  $'\x6'  $'\x7'  $'\x8'  
 64  $'\x9'  $'\xA'  $'\xB'  $'\xC'  $'\xD'  $'\xE'  $'\xF'  $'\x10'  $'\x11' 
 65  $'\x12'  $'\x13'  $'\x14'  $'\x15'  $'\x16'  $'\x17'  $'\x18'  $'\x19'  
 66  $'\x1A'  $'\x1B'  $'\x1C'  $'\x1D'  $'\x1E'  $'\x1F'  $'\x7f' $'\x80' 
 67  $'\x81'  $'\x82'  $'\x83'  $'\x84'  $'\x85'  $'\x86'  $'\x87'  $'\x88'  
 68  $'\x89'  $'\x8A'  $'\x8B'  $'\x8C'  $'\x8D'  $'\x8E'  $'\x8F'  $'\x90'  
 69  $'\x91'  $'\x92'  $'\x93'  $'\x94'  $'\x95'  $'\x96'  $'\x97'  $'\x98'  
 70  $'\x99'  $'\x9A'  $'\x9B'  $'\x9C'  $'\x9D'  $'\x9E'  $'\x9F' ) 
 71
 72
 73if [ "$enable_illchars" = yes ]; then
 74
 75  illname=(\( -false)
 76  for ((i=0; $i<${#illchars[@]}; i=$i+1)) 
 77  do
 78    #handle bash \x7f error
 79    if [ "*${illchars[$i]}*" = "**" ]; then
 80      illchars[$i]=$'\x80'  #'
 81    fi
 82    illname[${#illname[@]}]='-or'
 83    illname[${#illname[@]}]=-name
 84    illname[${#illname[@]}]="*${illchars[$i]}*"
 85  done
 86  illname[${#illname[@]}]=')'
 87
 88  illlink=(\( -false)
 89  for ((i=0; $i<${#illchars[@]}; i=$i+1)) 
 90  do
 91    illlink[${#illlink[@]}]='-or'
 92    illlink[${#illlink[@]}]=-lname
 93    illlink[${#illlink[@]}]="*${illchars[$i]}*"
 94  done
 95  illlink[${#illlink[@]}]=')'
 96else #if [ "$enable_illchars" = no ]
 97  illlink=(-false)
 98  illname=(-false)
 99fi
100
101# $1=section heading
102# $2=inode message
103report()
104{
105  echo -printf "increment_code_here"
106  echo -printf 
107  echo "1 ${1}\\n" | sed 's/ /\\040/g'
108  echo -printf "insert_code_here"
109  
110  if [ -n "$2" ]; then
111    echo -printf 
112    echo "2 %i 1 ${2}\\n" | sed 's/ /\\040/g'
113    echo -printf "insert_code_here"
114    echo -printf 
115    echo "2 %i 2 " | sed 's/ /\\040/g'
116  else
117    echo -printf "2\\040" 
118  fi
119  
120  echo -exec ls -T 0 -ladQ {} \;
121}
122
123
124filegoodperm=(\( -perm 644 -or -perm 755 -or -perm 555 -or -perm 444 -or -perm 600 -or -perm 700 -or -perm 640 \))
125dirgoodperm=(\( -perm 755 -or -perm 555 -or -perm 700 -or -perm 750 \))
126
127good=( \( 
128          -not \( -not -type d -links +1 \)
129          -not -nouser -not -nogroup 
130          -not \( "${illname[@]}" \) 
131          -not \( "${illlink[@]}" \) 
132       \) 
133       -and
134\(
135      \( -type f -not -group install "${filegoodperm[@]}" \) 
136  -or \( -type d -not -group install "${dirgoodperm[@]}" \)
137  -or \( -type d -group install \( -perm 1775 \) \)
138  -or \( -type d -group root -user root -path "/tmp" \( -perm 1777 \) \)
139  -or \( -type d -group root -user root -path "/var/tmp" \( -perm 1777 \) \)
140  -or \( -not -type d -not -type f -not -type l -path "/dev/*" \)
141  -or \( -type l \( -xtype b -or -xtype c -or -xtype d -or -xtype p -or -xtype f \) \)
142\)
143)
144
145bad=(
146    \( "${illname[@]}" $(report  "NON-PRINTABLE CHAR IN NAME")  \)
147 OP \( "${illlink[@]}" $(report  "NON-PRINTABLE-CHAR IN LINK-TARGET")  \)
148 OP \( -type f -perm -4000 $(report  "SETUID FILES")  \)
149 OP \( -type f -perm -2000 $(report  "SETGID FILES")  \)
150 OP \( -type f -perm -1000 $(report  "STICKY FILES")  \)
151 OP \( -type d -perm -2000 $(report  "GROUP-KEEPING DIRECTORIES")  \)
152 OP \( -type d -not -group install -perm -1000  $(report  "STICKY DIRECTORIES")  \)
153 OP \( -type f -perm -g+w  $(report  "GROUP-WRITABLE FILES")  \)
154 OP \( -type f -perm -o+w  $(report  "WORLD-WRITABLE FILES")  \)
155 OP \( -type d -perm -g+w  $(report  "GROUP-WRITABLE DIRECTORIES")  \)
156 OP \( -type d -perm -o+w  $(report  "WORLD-WRITABLE DIRECTORIES")  \)
157 OP \( -not \( -type f -or -type l -or -type d \) -not -path "/dev/*"  $(report  "SPECIAL FILES OUTSIDE /dev")  \)
158 OP \( -type d -group install -not -perm 1755  $(report  "INSTALL DIRECTORIES WITH UNUSUAL PERMISSIONS")  \)
159 OP \( -type f -group install $(report  "FILES ASSIGNED TO GROUP INSTALL")  \)
160 OP \( -type l -not \( -xtype b -or -xtype c -or -xtype d -or -xtype p -or -xtype f \) $(report  "SYMLINKS POSSIBLY BROKEN OR LOOP")  \)
161 OP \( -not -type d -links +1 $(report "HARDLINKED FILES" "Inode %i is shared by %n files, including") \)
162 OP \( -nouser  $(report  "THINGS HAVING UID WITH NO ASSIGNED USER NAME")  \)
163 OP \( -nogroup  $(report  "THINGS HAVING GID WITH NO ASSIGNED GROUP NAME")  \)
164 OP \( -type f -not -group install -not "${filegoodperm[@]}"  $(report  "FILES WITH UNUSUAL PERMISSIONS")  \)
165 OP \( -type d -not -group install -not "${dirgoodperm[@]}"  $(report  "DIRECTORIES WITH UNUSUAL PERMISSIONS")  \)
166)
167
168#insert unique codes for the messages
169code=100
170for ((i=0; $i<${#bad[@]}; i=$i+1)) 
171do
172  if [ "${bad[$i]}" = "increment_code_here" ]; then
173    code=$(($code + 1))
174    bad[$i]=$code
175  elif [ "${bad[$i]}" = "insert_code_here" ]; then
176    bad[$i]=$code
177  fi
178done
179
180allbad=()  #all bad matches are reported
181onebad=()  #only the first bad match is reported
182for ((i=0; $i<${#bad[@]}; i=$i+1)) 
183do
184  if [ "${bad[$i]}" = "OP" ]; then
185    allbad[$i]=","
186    onebad[$i]="-or"
187  else
188    allbad[$i]="${bad[$i]}"
189    onebad[$i]="${bad[$i]}"
190  fi
191done
192
193#Add a default case to onebad.
194#This should never be hit, because the explicit cases should catch all
195#files, but just in case I've missed something, this will catch it.
196onebad=("${onebad[@]}" -or  $(report  "WEIRD SHIT") )
197
198#make allbad always return false
199allbad=("${allbad[@]}" , -false)
200
201cmd=( "${usergroupmatch[@]}" -and
202     \( \( "${good[@]}" \) -or \( "${allbad[@]}" \) -or \( "${onebad[@]}" \) \)
203    )
204
205#In the following find command, the part
206# -not ( ( "${y[@]}" -prune ) -or "${y[@]}" )
207#is responsible for preventing the files that match prune_prefixes from
208#being processed. The 2nd "${y[@]}" may seem redundant, but it isn't, because
209#-prune has no effect and is always false when -depth is used.
210find "${fs_to_scan[@]}" -xdev -noleaf \
211     -not \( \( "${y[@]}" -prune \) -or "${y[@]}" \) \
212     -and \( "${cmd[@]}" \) | 
213sed 's/^\(...2\) \([0-9]\+ 2 \)\?\([^ ]\+\) \+[^ ]\+ \+\([^ ]\+\) \+\([^ ]\+\) \+[^"]\+\(".\+\)/\1 \2\3 \6  \4:\5/' |
214sort -u | 
215sed 's/^...1 /\'$'\n''/;s/^...2 [0-9]\+ 1 /\'$'\n''  /;s/^...2 [0-9]\+ 2 /    /;s/^...2 /  /'

/bin/list_suspicious_files_from

bash
 1#!/bin/bash
 2# Copyright (c) 2004 Matthias S. Benkmann <article AT winterdrache DOT de>
 3# You may do everything with this code except misrepresent its origin.
 4# PROVIDED `AS IS' WITH ABSOLUTELY NO WARRANTY OF ANY KIND!
 5
 6if [ $# != 1 -o "$1" = "--help" ]; then
 7  echo 1>&2
 8  echo 1>&2 'USAGE: '"${0##*/}"' <user_or_group>'
 9  echo 1>&2
10  echo 1>&2 '  Outputs a categorized list of files and directories with properties'
11  echo 1>&2 '  that could mean trouble and should be investigated.'
12  echo 1>&2 '  Suspicious objects will be reported only if group and/or user equals'
13  echo 1>&2 '  <user_or_group> (numeric UID/GID allowed).'
14  echo 1>&2 '  This script calls `'"${0%_*}'"' for the real work.'
15  echo 1>&2
16  exit 1
17fi
18
19ugname="$1"
20
21ugmatcher=(-false)
22#test if find accepts ugname as a user, and append to ugmatcher if it does
23if find / -maxdepth 0 -user "$ugname" >/dev/null 2>&1 ; then
24  ugmatcher[${#ugmatcher[@]}]="-or"
25  ugmatcher[${#ugmatcher[@]}]="-user"
26  ugmatcher[${#ugmatcher[@]}]="$ugname"
27fi
28#test if find accepts ugname as a group, and append to ugmatcher if it does
29if find / -maxdepth 0 -group "$ugname" >/dev/null 2>&1 ; then
30  ugmatcher[${#ugmatcher[@]}]="-or"
31  ugmatcher[${#ugmatcher[@]}]="-group"
32  ugmatcher[${#ugmatcher[@]}]="$ugname"
33fi
34
35#if find accepted ugname as neither user nor group, then exit
36if [ "${#ugmatcher[@]}" = 1 ]; then
37  echo 1>&2 'find does not accept `'"$ugname'"' as group or user name'
38  exit 1
39fi
40
41"${0%_*}" "${ugmatcher[@]}"
嗨! 这里是 rqdmap 的个人博客, 我正关注 GNU/Linux 桌面系统, Linux 内核 以及一切有趣的计算机技术! 希望我的内容能对你有所帮助~
如果你遇到了任何问题, 包括但不限于: 博客内容说明不清楚或错误; 样式版面混乱; 加密博客访问请求等问题, 请通过邮箱 rqdmap@gmail.com 联系我!
修改日志
  • 2023-09-01 18:14:49 单独划分ACM专题; 移动部分博客进入黑洞归档
  • 2023-05-29 23:05:14 博客结构与操作脚本重构
  • 2023-05-08 21:44:36 博客架构修改升级
  • 2023-03-02 18:49:25 阅读了部分LFS包管理源码