在Linux中,文件名中仅禁止使用两个字符“斜杠”和“空字符”。因此,每种脚本语言中具有特殊含义的每个字符都应该转义,但文件名中也允许每个转义序列!更糟糕的是,ie bash 的一些转义方法只转义某些字符,因此要转义大量不同的字符,您应该一起使用几种不同的转义方法,但它们会互相干扰!更糟糕的是,某些命令使用某些字符来达到其目的,而其他命令则使用其他字符,因此对于文件上的每个简单操作,您应该以不同的方式转义文件名!更糟糕的是,只能使用空字符来安全地分隔文件名,但大多数命令都无法使用它。更糟糕的是,在 Linux 中基本上一切都是文件......所以这看起来不仅令人讨厌,而且还关系到安全性和稳定性,因为 Linux 的很大一部分是基于脚本的,所以非常有缺陷!
那么请告诉我哪里错了......是否有可能正确处理所有可能的文件名?
澄清。本来我想:
列出给定路径下的文件和文件夹
搜索列表以查找符合给定条件(年龄或文件模式或大小)的内容
- 将匹配的文件和文件夹移动到类别(即电影)由于测试的复杂性,不可能(或不实际)在一个命令中完成此操作,因此我必须在不同的命令之间传递文件名。由于文件名中存在空格,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 对我们当前正在处理的路径名或文件名进行分词或文件名匹配。
了解更多信息: