是否有可能正确处理所有可能的文件名?

是否有可能正确处理所有可能的文件名?

在Linux中,文件名中仅禁止使用两个字符“斜杠”和“空字符”。因此,每种脚本语言中具有特殊含义的每个字符都应该转义,但文件名中也允许每个转义序列!更糟糕的是,ie bash 的一些转义方法只转义某些字符,因此要转义大量不同的字符,您应该一起使用几种不同的转义方法,但它们会互相干扰!更糟糕的是,某些命令使用某些字符来达到其目的,而其他命令则使用其他字符,因此对于文件上的每个简单操作,您应该以不同的方式转义文件名!更糟糕的是,只能使用空字符来安全地分隔文件名,但大多数命令都无法使用它。更糟糕的是,在 Linux 中基本上一切都是文件......所以这看起来不仅令人讨厌,而且还关系到安全性和稳定性,因为 Linux 的很大一部分是基于脚本的,所以非常有缺陷!

那么请告诉我哪里错了......是否有可能正确处理所有可能的文件名?

澄清。本来我想:

  1. 列出给定路径下的文件和文件夹

  2. 搜索列表以查找符合给定条件(年龄或文件模式或大小)的内容

  3. 将匹配的文件和文件夹移动到类别(即电影)由于测试的复杂性,不可能(或不实际)在一个命令中完成此操作,因此我必须在不同的命令之间传递文件名。由于文件名中存在空格,Bash 通配符是首先要放弃的。通配符总是将带空格的文件名拆分为列表的两个元素。然后我尝试使用“查找”。这更好,但速度慢得多,并且难以使用。

我无法使用任何特殊字符来转义文件名,因为我不知道文件名中可能包含什么字符。经过一些测试后,我发现任何字符出现只是时间问题。

我尝试过定义过滤器,例如: audio_ext=(*.mp3 *.wav *.ogg *.mid *.mod *.stm *.s3m *.it *.wma *.669 *.ac3) 很快我就意识到,通过这种方式我无法定义多种用途的过滤器,因为通配符会踢掉权利。所以我已经禁用了通配符和历史记录set -fH。如果没有通配符,我必须手动进行扩展

while IFS= read -r -d $'\0'; do list+=("$REPLY") done < <( find . -maxdepth 1 -mindepth 1 ${params[@]} -print0 2>/dev/null )

params像这样的数组在哪里"-iname" "*.mp3" "-o" "-iname" "*.wav"。这一直有效,直到文件名称中包含“(”。查找返回有关错误用法的错误。

说实话......直到最近我已经使用批处理脚本来完成这项任务 15 年了。写作的时间大约是一两个下午。它有缺点和!文件名问题,但通常它是有效的。现在我已经尝试了近两个月的时间来用 bash 编写它。它丑陋、复杂、漏洞百出,而且似乎永远无法正常工作。

答案1

简单的。使用 globbing 选择所需的文件,并引用保存文件名的变量:

shopt -s nullglob
for file in ./*.txt; do
    do_something_with "$file"
done

这就是全部内容了。

更多细节:


更新:通配符是不是造成您所看到的分词效果。未能引用该变量是。

您可以通过以下方式获取适合您的条件的文件信息stat

read size mtime < <(stat -c "%s %Y" "$file")
[[ $size -gt 1000 ]] && echo "too big"
[[ $mtime -lt $(date -d yesterday +%s) ]] && echo "too old"

更新 2:创建包含许多特殊字符的文件名需要混合各种引用机制,但仍然可以对该文件执行任何操作。

$ filename='~ASDFzxcv!@#$%^&*()_+[]\{}|;:",.<>?`'"'"$' \a\t\n\r\f'".txt"
#          ^^ single quoted part ^^^^^^^^^^^^^^^^   
#                             double quoted part ^^^
#                                ANSI-C quoted part ^^^^^^^^^^^^^^

$ echo "$filename"
~ASDFzxcv!@#$%^&*()_+[]\{}|;:",.<>?`'   

.txt

$ printf "%q\n" "$filename"
$'~ASDFzxcv!@#$%^&*()_+[]\\{}|;:",.<>?`\' \a\t\n\r\f.txt'

$ date > "$filename"

$ cat "$filename"
Thu Apr 12 15:14:29 EDT 2018

$ ls -lt
total 3836
-rw-rw-r-- 1 jackman jackman      29 Apr 12 15:14 ~ASDFzxcv!@#$%^&*()_+[]\{}|;:",.<>?`' ?????.txt

$ ls -lt --show-control-chars
total 3836
-rw-rw-r-- 1 jackman jackman      29 Apr 12 15:14 ~ASDFzxcv!@#$%^&*()_+[]\{}|;:",.<>?`'     

.txt

如果 的输出ls重定向到终端以外的任何内容(例如文件或管道),则--show-control-chars默认情况下它将使用该样式。您可以通过运行来看到这一点ls -lt | cat。  ls有其他显示选项;例如,。--quoting-style=WORD

答案2

文件名可以使用除 nul 字符 ( \0) 和斜杠(路径分隔符)之外的任何字符。变量可以保存任何数据(大多数 shell 中的 null 字符除外)。如果正确引用,文件名可以安全地存储在变量中并与实用程序一起使用。

关于你的观点:

要迭代一组文件(常规文件或目录),您可以使用简单的 shell 循环,例如

for name in ./*; do
    # some code that uses "$name"
done

在使用特定条件选择特定文件时迭代文件find是更好的选择。例如,要选择当前目录(或以下)中早于N几天的所有常规文件(修改日期至少N在过去几天):

find . -type f -mtime +N

类似地,-size用于根据大小选择文件,并将-name文件名与通配模式进行匹配。

例如,要选择文件名匹配*.mov且上周已修改的常规文件:

find . -type f -name '*.mov' -mtime -7

那么,实际上对这些文件进行一些操作,例如将它们移动到$HOME/Movies目录中:

find . -type f -name '*.mov' -mtime -7 -exec mv {} "$HOME/Movies" ';'

{}被调用时文件的路径名替换mv。您不需要引用{}(如果引用,它不会改变任何内容),因为find不会调用 shell 的分词或路径名上的文件名扩展。

对此的进一步改进是检测目标目录中的文件名冲突。为此,我们使用一个简短的帮助程序脚本,该脚本将在其命令行上获取多个文件名:

destdir="$HOME/Movies"
for name do
    if [ -f "$destdir/${name##*/}" ]; then
        printf "%s already exists in %s, not overwriting it!\n" "${name##*/}" "$destdir" >&2
    else
        mv "$name" "$destdir"
    fi
done

或者,以快捷形式:

destdir="$HOME/Movies"
for name do
    [ -f "$destdir/${name##*/}" ] && printf "skipping %s\n" "$name" >&2 && continue
    mv "$name" "$destdir"
done

将其插入我们的find命令中:

find . -type f -name '*.mov' -mtime -7 -exec sh -c '
    destdir="$HOME/Movies"
    for name do
        [ -f "$destdir/${name##*/}" ] && printf "skipping %s\n" "$name" >&2 && continue
        mv "$name" "$destdir"
    done' sh {} +

在此过程中,我们不允许 shell 对我们当前正在处理的路径名或文件名进行分词或文件名匹配。

了解更多信息:

相关内容