我的文件夹中有 11 个名称中带有空格的文件,我想复制最新的 10 个文件。我过去ls -t | head -n 10
只能获取最新的 10 个文件。当我想在 cp 语句中使用表达式时,出现错误,指出由于名称中存在空格而无法找到文件。
例如:cp: cannot stat ‘10’: No such file or directory
对于名为10 abc.pdf
cp声明:cp $(ls -t | head -n 10) ~/...
我怎样才能让它发挥作用?
答案1
如果您正在使用 Bash 并且想要一个 100% 安全的方法(我想您也想要这个方法,因为您已经通过惨痛的教训学会了必须认真处理文件名),那么您可以:
shopt -s nullglob
while IFS= read -r file; do
file=${file#* }
eval "file=$file"
cp "$file" Destination
done < <(
for f in *; do
printf '%s %q\n' "$(date -r "$f" +'%s')" "$f"
done | sort -rn | head -n10
)
我们来看看它的几个部分:
for f in *; do
printf '%s %q\n' "$(date -r "$f" +'%s')" "$f"
done
这将打印到表单的标准输出条款
Timestamp Filename
其中时间戳是自纪元以来的修改日期(通过-r
的选项获得date
)以秒为单位,文件名是文件名的带引号版本可以重复用作 shell 输入(请参阅help printf
和%q
格式说明符)。然后将这些行用 排序(按数字顺序,以相反的顺序)sort
,并且仅保留前 10 行。
然后将其送入while
循环。时间戳会随着赋值被删除file=${file# *}
(这会删除第一个空格之前的所有内容),然后明显危险的行eval "file=$file"
会删除 引入的转义字符printf %q
,最后我们可以安全地复制文件。
可能不是最好的方法或实现,但 100% 保证任何可能的文件名的安全,并完成工作。不过,这将同样对待常规文件、目录等。如果您想限制为常规文件,请[[ -f $f ]] || continue
在该for f in *; do
行后面添加。此外,它不会考虑隐藏文件。如果您想要隐藏文件(当然.
也不是..
),请添加shopt -s dotglob
.
另一种 100% Bash 解决方案是直接使用 Bash 对文件进行排序。这是使用快速排序的方法:
quicksort() {
# quicksorts the filenames passed as positional parameters
# wrt modification time, newest first
# return array is quicksort_ret
if (($#==0)); then
quicksort_ret=()
return
fi
local pivot=$1 oldest=() newest=() f
shift
for f; do
if [[ $f -nt $pivot ]]; then
newest+=( "$f" )
else
oldest+=( "$f" )
fi
done
quicksort "${oldest[@]}"
oldest=( "${quicksort_ret[@]}" )
quicksort "${newest[@]}"
quicksort_ret+=( "$pivot" "${oldest[@]}" )
}
然后,将它们整理出来,保留前 10 个,并将它们复制到您的目的地:
$ shopt -s nullglob
$ quicksort *
$ cp -- "${quicksort_ret[@]:0:10}" Destination
与之前的方法相同,这将同等对待常规文件、目录等并跳过隐藏文件。
对于另一种方法:如果您ls
有i
和q
参数,您可以使用以下内容:
ls -tiq | head -n10 | cut -d ' ' -f1 | xargs -I X find -inum X -exec cp {} Destination \; -quit
这将显示文件的索引节点,并且find
可以对其索引节点引用的文件执行命令。
同样,这也将处理目录等,而不仅仅是常规文件。我不太喜欢这个,因为它太依赖于ls
的输出格式......
答案2
对于目录中的任何文件组,这将始终忽略最旧的文件:
less_oldest() {
while [ -e "$1" ] ; do
for f do [ "$f" -ot "$1" ] && break
done && { set -- "$@" "$1"
shift ; continue
} ; shift ; break
done
printf '///%s///\n' "$@" |
sed "s|'"'|&"&"&|g;'"s|///|'|g"
}
它是完全可移植的,并且它会打印安全的 shell 引用的文件名数组,无论它们可能返回什么字符。在您的情况下,因为您只需要 dro pone,所以您只需要运行一次,如下所示:
{ printf 'cp -t ~"/..."'
less_oldest "${dir_of_11_files}/"*
} | sh