我读过 Jay Lacroix 的《精通 Ubuntu 服务器》一书,他建议删除所有不必要的软件包,以减少攻击面。具体来说,他建议运行apt-cache rdepends <package>
以查明是否有其他软件包依赖于我们考虑删除的软件包。
我编写了一个 bash 脚本,列出了所有已安装软件包的依赖软件包,但是它花费了很长时间(在 Raspberry Pi 4、8GB 上需要 30 分钟以上),我想知道是否有更好、更快的解决方案。
#!/bin/bash
readarray -t packages < <(dpkg --get-selections | cut -f1)
for package in ${packages[@]};
do
readarray -t dependents < <(apt-cache rdepends $package | sed -n '3,$s/^\s*//p')
echo "-----------------------------------------------------------------------" | tee -a packages_and_depents.txt
echo "${package} has these dependents on the system of max ${#dependents[@]}:" | tee -a packages_and_depents.txt
echo "-----------------------------------------------------------------------" | tee -a packages_and_depents.txt
for dependent in ${dependents[@]};
do
dpkg --get-selections $dependent 2>/dev/null | tee -a packages_and_depents.txt
done
done
答案1
包系统dpkg
为每个包都有一个字段,指示其优先事项。
optional
您可以将其用作初始过滤器,并且仅对归类为和extra
(并省略required
、important
和)的包运行脚本standard
。
此外,为每个包创建额外的数组并运行额外的for
循环似乎是不必要的,而且肯定会消耗更多的计算能力。
因此我删除了第二个for
循环并直接将其添加--installed
到apt-cache rdepends
命令中。
可以通过修改脚本来实现,如下所示:
#!/bin/bash
# The command for this line is changed
readarray -t packages < <(dpkg-query -Wf '${Package}${Status}${Priority}\n' | sort -b -k5,5 -k1,1 | grep -v 'required\|important\|standard' | grep 'installed' | awk '{ print $1 }')
for package in ${packages[@]};
do
echo "--------------------------------------------------------" | tee -a packages_and_depents.txt
echo "${package} has these dependents installed on the system:" | tee -a packages_and_depents.txt
echo "--------------------------------------------------------" | tee -a packages_and_depents.txt
# 2nd for loop removed and replaced with `--installed` option
apt-cache --installed rdepends "$package" | tail -n +3 | tee -a packages_and_depents.txt
done
另一个选择是更改整个脚本,因此它不显示所有反向依赖项,而是仅显示那些没有反向依赖项的包(那些可以删除的包)的名称。
此外,我认为您可以grep
通过排除所有名称以lib
(添加grep -v '^lib'
)开头的包来添加额外的排除项。
最后,可以改进演示,因此脚本在运行时提供视觉反馈,但最终报告仅写入输出文件。
这是我的最终版本的脚本:
#!/bin/bash
# The command for this line is changed
readarray -t packages < <(dpkg-query -Wf '${Package}${Status}${Priority}\n' | sort -b -k5,5 -k1,1 | grep -v 'required\|important\|standard' | grep -v '^lib' | grep 'installed' | awk '{ print $1 }')
# Write to file
echo "The following packages are not a dependency to any installed package:" > packages_no_depends.txt
# Write to screen
echo "Number of packages: [ ${#packages[@]} ] (priority optional/extra)"
echo ""
i=0
j=0
# Loop that only prints package names with NO reverse dependencies
for package in ${packages[@]};
do
(( j++ ))
echo -e "\033[1AProcessed packages: [ $i/$j ]"
if [[ $(apt-cache --installed rdepends "$package" | tail -n +3 | wc -l) -eq 0 ]]
then
# Write to file
echo " $package" >> packages_no_depends.txt
# Write to screen
echo -e "\033[K Package $package added to the list of non-dependencies\033[1A"
(( i++ ))
fi
done
# Final overview
echo -e "\033[K"
echo "STATUS"
echo "======"
echo " Total packages scanned : $j"
echo " Candidates for removal : $i"
echo " Script execution time : $SECONDS seconds"
参考光标移动的转义码。
编辑:这个解决方案主要应该考虑布局和演示 - Raffa 的解决方案更为有效,因此请根据喜好将两者进行自己的组合。
根据 Raffa 的意见,这是我的最终脚本版本:
#!/bin/bash
# Change /path/to
dpkg_file="/path/to/packages_no_depends.txt"
# Write to file
echo "The following packages are not a dependency to any installed package:" > "$dpkg_file"
# Write to screen
echo "Scanning packages ..."
# Function to write non-dependencies to file
dpkg-query -Wf '${Package} ${Status}${Priority}\n' |
grep -v 'required\|important\|standard' |
grep 'installed' |
awk '{ print $1 }' |
xargs apt-cache rdepends --installed |
awk '! /Reverse Depends:/ {
tp = $0
n++
}
/Reverse Depends:/ {
if (n == 1 && NR != 2) {
print " " p
}
n = 0
p = tp
}
END {
if (n == 0) {
print " " p
}
}' >> "$dpkg_file"
# Final overview
echo -e "\nSTATUS\n======"
echo " Total packages scanned : $(dpkg-query -Wf '${Package}${Status}${Priority}\n' | grep -v 'required\|important\|standard' | grep 'installed' | wc -l)"
echo " Candidates for removal : $(tail -n +2 $dpkg_file | wc -l)"
echo " Script execution time : $SECONDS seconds"
答案2
根据您在脚本中实现的工具,这应该是尽可能快的:
dpkg --get-selections |
cut -f1 |
xargs apt-cache rdepends --installed |
awk '! /Reverse Depends:/ {
tp = $0
n++
}
/Reverse Depends:/ {
if (n == 1 && NR != 2) {
print p
}
n = 0
p = tp
}
END {
if (n == 0) {
print p
}
}
'
它应该在输出中列出dpkg --get-selections | cut -f1
系统上没有安装反向依赖项的软件包。
!!警告!!
切勿将上述命令的输出提供给软件包删除工具...在所有情况下都检查输出并手动处理。
答案3
这是我的最终实施来自@Raffa、@artur-meinild 和@Dan 的意见。
我在 Raspberry Pi 4 (8GB) 和 iMac (8GB、i5、SSD、2015) 上的 VM 上测试了该脚本,两个系统上的运行时间均为 1 秒左右。与我最初耗时超过 30 分钟的脚本相比,这是一个很大的改进。
谢谢大家!
#!/bin/bash
# Define output filename
filename=packages_no_dependents.txt
# Write heading to file
echo "The following packages are not a dependency to any installed package:" > $filename
# Get all installed packages that do not have a 'Priority' of 'required', 'important' or 'standard' and do not have packages that depend on them
dpkg-query -Wf '${Package} ${Status;-26}${Priority}\n' | grep -v 'required\|important\|standard' | grep 'installed' | awk '{ print $1 }' |
xargs apt-cache rdepends --installed |
awk '! /Reverse Depends:/ {
tp = $0
n++
}
/Reverse Depends:/ {
if (n == 1 && NR != 2) {
print p
}
n = 0
p = tp
}
END {
if (n == 0) {
print p
}
}
' | tee -a $filename
# Remove leading white-space in file
sed -i '3,$s/\s*//' $filename
答案4
我通常采用另一种方式:aptitude
我将所有内容标记为“自动安装”,这将标记除必需软件包之外的所有软件包以供删除。然后,我会浏览要卸载的软件包列表,并手动添加那些我知道我会卸载的软件包直接地需要。
如果列表中的某些内容看起来有用,我可以按r
获取反向依赖项列表,如果该列表中的某些内容看起来有用,则会将其标记为手动安装,而我之前查看的包仍然作为依赖项安装。