LFS包管理与Shell脚本
参考文档
More Control and Package Management using Package Users (v1.4)
工具包: more_control_helpers
Shell 编程
特殊变量
$开头的一些特殊变量:
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:
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目录.
首先是一些提示信息以及基本的参数读取方式:
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>
的参数, 而能保持其余的参数不受到影响.
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看该包用户是否已经存在.
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查找算法, 没有语法上的困难, 就不赘述了.
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指示新包对应的组.. 代码观感一言难尽不过…
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采用默认拒绝的方案, 十分的安全
-
正则
{[^.],.[^.],..?}*
没太搞懂, 留坑.
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的功能.
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提取了不包含可能存在的路径的纯文件名.
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.
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前加个#
则会统计数组长度.
那么再看到下面的代码:
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
限制了搜索的深度, 因而其实不要求其进行任何实际的搜索, 只是做一个合法性检查.
一个简单例子是:
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
即可禁用该优化强制搜索所有的子目录.
- 读了半天好像也不太懂?… 大概意思可能是, 如果一个文件夹的hard links数量=子文件夹数量+2(这里具体可以参考Linux文件权限), 那么该目录的其他入口就不是一个文件夹了, 言下之意是说其余的入口就是符号链接了? 那么find就会将其优化掉不再搜索一遍了, 提高了搜索速度. 通过
-
-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
等.
- 第一个not中用于剪枝, 为什么要有第二个
/bin/uninstall_package
安全的删除指令, 执行后并不会实际删除任何文件或文件夹, 而是再给出一段调用forall_direntries_from的指令, 复制并执行后也不会真正删除文件, 而是echo出将要删除的文件名, 等待用户进一步确认后才会动手, 非常的好.
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的文件, 可以随意写的文件, 烂掉的软连接, 硬链接等.
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
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[@]}"