如何改进这个 bash shell 脚本以将硬链接转换为符号链接?

如何改进这个 bash shell 脚本以将硬链接转换为符号链接?

这个 shell 脚本主要是其他人的作品人们。它经历了几次迭代,我对其进行了轻微的调整,同时也试图完全理解它的工作原理。我想我现在明白了,但我没有信心自己对其进行重大修改,否则运行修改后的版本时可能会丢失数据。所以我很感激一些专家指导如何改进这个脚本。

我所寻求的改变是:

  1. 如果可能的话,让它对任何奇怪的文件名更加健壮。它目前处理文件名中的空格,但不处理换行符。我可以忍受这一点(因为我尝试找到任何带有换行符的文件名并删除它们)。
  2. 使其更加智能地确定哪些文件将保留为实际的 inode 内容,哪些文件将成为符号链接。我希望能够选择保留以下文件:a) 路径最短的文件,b) 路径最长的文件,或 c) 文件名中包含最多字母字符的文件(这可能是最具描述性的名称)。
  3. 允许它从传入的参数或文件读取要处理的目录。
  4. 可选地,写下所有更改和/或所有未处理的文件。

在所有这些中,#2 对我来说是目前最重要的。我需要用它处理一些文件,并且需要改进它选择将哪些文件转换为符号链接的方式。(我尝试使用诸如 find 选项 -depth 之类的东西,但没有成功。)

这是当前的脚本:

#!/bin/bash

# clean up known problematic files first.
## find /home -type f -wholename '*Icon*
## *' -exec rm '{}' \;

# Configure script environment
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
set -o nounset
dir='/SOME/PATH/HERE/'

# For each path which has multiple links
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# (except ones containing newline)
last_inode=
while IFS= read -r path_info
do
   #echo "DEBUG: path_info: '$path_info'"
   inode=${path_info%%:*}
   path=${path_info#*:}
   if [[ $last_inode != $inode ]]; then
       last_inode=$inode
       path_to_keep=$path
   else
       printf "ln -s\t'$path_to_keep'\t'$path'\n"
       rm "$path"
       ln -s "$path_to_keep" "$path"
   fi
done < <( find "$dir" -type f -links +1 ! -wholename '*
*' -printf '%i:%p\n' | sort --field-separator=: )

# Warn about any excluded files
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
buf=$( find "$dir" -type f -links +1 -path '*
*' )
if [[ $buf != '' ]]; then
    echo 'Some files not processed because their paths contained newline(s):'$'\n'"$buf"
fi

exit 0

答案1

1.

一个简单的更改,可以避免因以 开头的文件名而死,-就是--在文件名参数开始之前添加(表示“现在所有选项都已给出,只剩下位置参数”),例如

rm -- "$path"
ln -s -- "$path_to_keep" "$path"

等等。


2.

要计算文件名中的字母(“字母数字”可能是你真正想要的)字符,你可以这样做

numberofalnum=$(printf -- "$path" | tr -cd [:alnum:] | wc -m)

要计算路径深度,您可以尝试仅计算文件名中“/”的出现次数。需要注意的是,这/home///daniel相当于/home/daniel,但find不会输出不必要的多个斜杠,所以没问题。

depth=$(printf -- "$path" | tr -cd / | wc -m)

也可以通过tr -s /在 之后运行 来折叠多个斜杠printf。看来-s,在一次调用中以这种方式组合-c和实际上是行不通的。-d

在这种情况下,由于find脚本中已经以这种方式使用了,因此只需在输出中添加一个:分隔的字段就会直接打印深度,如下面注释中所述。-printf%d


3a.

要从命令行读取目录作为参数,请参阅以下最简短的代码片段:

#!/bin/sh
i=0
while [ $# -ne 0 ]; do
    printf -- 'Argument %d: %s\n' "${i}" "${1}"
    i=$((i+1))
    shift
done

$i只是一个计数器,用来显示正在发生的事情)

如果您将逻辑包装在这样的 while 循环中,则可以将第一个参数作为 访问${1},然后使用shift弹出参数列表中的第一个项目,然后再次迭代,现在${1}就是最初的第二个参数。在参数计数不为 0 时执行此操作$#


3b.

要从文件中读取参数,请将其包装成

#!/bin/sh
i=1
while read line; do
    printf -- 'Argument %d: %s\n' "${i}" "${line}"
    i=$((i+1))
    shift
done < "${1}"

提示:不要仅仅增加缩进并以这种方式包装整个文件逻辑,而是创建当前逻辑的函数并在脚本末尾调用它们。这将使您能够轻松地选择将目录作为参数或从文件中读取它们,而无需在脚本中重复代码。


4.

添加

printf 'My descriptive log message for path %s\n' "${path}" >> "${logfile}"

在逻辑块中,您已决定是否采取行动。 先将其设置$logfile为所需的日志路径。

相关内容