我正在尝试将五个最大的文件从某个目录复制到我的pwd
.使用cp specific/directory$(ls -S specific/directory | head -n) ./
复制第一个文件,然后继续生成cannot stat
为列表中的其余文件产生错误。
为什么管道对第一个项目有效而对其余项目失败?
答案1
我所有的解决方案都治疗仅文件,根据要求,并且可以处理所有类型的文件
(即使有特殊字符)。
如果你想使用ls -S
以正确的方式做:
ls --zero -S | head -z -n5 | xargs -r0 cp -t ./other/dir --
要求最近GNU
coreutils
.
coreutils 9.1-1
这里。
另一种方式,使用bash
和最近GNU
find
:
findutils 4.9.0-4
这里。
基于在此:
shopt -s nullglob
cd specific/directory/ || exit
print0 () {
[ "$#" -eq 0 ] || printf '%s\0' "$@"
}
readarray -td '' files < <(
print0 * |
find -files0-from - -maxdepth 0 -type f -printf '%b\t%p\0' |
sort -rzn |
cut -zf2 -
)
cp -av -- "${files[@]:0:5}" "$OLDPWD"/
${files[@]:0:5}
正在扩展到前 5 个元素文件键大于或等于 0 的数组。
对于旧工具,通过Perl
任何 shell
perl -e 'rename($_, "./other/dir/$_") for ((sort { -s $b <=> -s $a } <*>))[0..4]'
答案2
使用zsh
您可以避免与解析和排序输出相关的所有陷阱ls
:
cp -n -- specific/directory/*(.DOL[1,5]) ./
或使用 GNU cp
(用于-t
选项):
cp -n -t ./ -- specific/directory/*(.DOL[1,5])
哪里的全局限定符是
.
仅匹配普通文件(不匹配目录、符号链接、fifo、套接字。)D
切换 dotglob 选项 - 如果您想排除隐藏文件,请忽略此选项OL[1,5]
按文件长度(大小)对结果进行排序并选择前 5 个
该-n
选项可以防止cp
在名称冲突的情况下破坏现有文件。
答案3
整合其他答案:
TL;博士:bash
请参阅下面的POSIX shell的可行解决方案。
为什么管道对第一个项目有效而对其余项目失败?
因为 shell 的行为与您的命令所假设的不同。
命令$(ls -S | head)
替换确实被其输出替换,并且确实粘贴在紧邻代码片段右侧的位置cp specific/directory
,但是:
- 因为您没有用双引号引用它(这本身就是错误的),所以命令替换的输出会根据变量进行分词
IFS
;后者默认设置为(单个空格)加 <tab> 加 <newline> 字符,并且 <newline> 恰好是命令
ls -S | head
用来分隔每个文件名的内容,因此每个名称最终都是一个单独的独立路径听从您的cp
命令;请注意,在这种情况下,双引号命令替换不会有帮助,因为您可能已经发现 - shell 也不会复制
specific/directory/
每个名称的片段; (这将是支撑扩展的工作,但在这种情况下要做好它会很棘手);因此,只有第一个这样单独的名称获得目录前缀,因此可以通过 访问cp
,而其他 4 个名称预计会出现在当前目录中,但显然它们不是(即使是,cp
也会有然后抱怨它们实际上与目标目录中的文件相同./
)
可以让它“工作”吗?原则上是的,但它很脆弱,因为一旦 n 个文件之一包含变量中指定的字符之一,它就会崩溃IFS
;更糟糕的是,如果eval
您无法完全控制specific/directory
. (另外,请参阅下面的注释1)。
bash
POSIX shell 的可能解决方案
除了其他答案中提到的使用 GNU coreutils v9.0 及以上版本时可用的解决方案之外,还可以使用coreutils v8.25(大约 2016)及以上版本的 GNU 安全地完成该操作,它提供了shellls --zero
的变体。为此我们ls
--quoting-style
需要使用eval
,因为这实际上是从该ls
选项中受益的唯一方法,该选项确实被设计为有效和 eval
。
像往常一样,eval
如果有的话,需要格外小心地处理。在这里,我们仅将它用于ls
命令,并依赖于ls
根据记录的行为为 shell 正确引用文件名。为了额外的注意,人们可以调用例如提供所需选项的可执行文件/bin/ls
的显式完整路径,而不是冒险使用谁知道哪个恰好在或谁知道故意命名的导出流氓函数(甚至别名)。ls
--quoting-style
ls
$PATH
ls
所以,与bash
:
(
set -o pipefail \
&& o="$(/bin/ls -S --quoting-style=shell-escape-always | head -n 5)" \
&& eval "set -- $o" \
&& (("$#")) && cp -n -- "${@/#/specific/directory/}" .
)
您可以通过更改head -n 5
.
请注意,在上面的代码片段中,我添加了额外的安全和错误检查,但实际上,整个事情可以精简为基本命令,如果你是绝对积极关于您的ls
版本,它没有真正的原因失败或输出杂散字符。
(cd specific/directory && \
eval "cp -n -- $(ls -S --quoting-style=shell-escape-always | head -n 5)"' "$OLDPWD"')
上述针对 POSIX shell 的解决方案的等效方案也可以安全地工作1,尽管它并不完全理想,因为它需要将命令提供的整个文件列表加载到内存中ls
。由于我们无法在此类列表到达 shell 之前过滤掉该列表,因此源目录不得包含足够多的文件以填充可用内存,否则 shell 将在运行命令之前终止cp
:
(
set -- && cd specific/directory \
&& o="$(/bin/ls -rSxw 0 --quoting-style=shell-always)" && eval "set -- $o" \
&& [ "$#" -gt 0 ] && n="$(($# - 5))" && shift "$(($n > 0 ? $n : 0))" \
&& cp -n -- "$@" "$OLDPWD"
)
在这里,您可以通过更改位来更改前 n 个文件的数量$(($# - 5))
。
就像这个bash
版本一样,只要您再次确定所需的先决条件,这个版本也可以稍微精简一下。这个除了bash
精简版本之外,还需要至少 n 个文件实际上存在于源目录中,否则shift
命令将失败,导致 shell 过早中止(例如,如果 中的文件少于 5 个specific/directory
,则此精简版本将不会复制它们)。
(
set -- && cd specific/directory \
&& eval "set -- $(ls -rSxw 0 --quoting-style=shell-always)" \
&& shift "$(($# - 5))" && cp -n -- "$@" "$OLDPWD"
)
1
笔记:为了简单和解释,上面的解决方案不是检查文件是否确实存在常规的仅文件(即不是目录或符号链接、套接字、命名fifos、设备文件)。因此如果你的源目录做碰巧在第一个最大的 n 个文件中存在此类“文件”(即使有效计数为 0 字节),上述解决方案将要在最终cp
命令中包含这些名称。这对于符号链接和目录特别相关总是确实计数大于 0,具体取决于其内容,因此可能排名高于ls -S
.当然,我们可以循环文件名来测试它们的文件类型并丢弃非常规文件,但是它会变得越来越复杂,特别是用下一个级别替换丢弃的文件。请参阅其他答案来理智地处理这些情况,因为我这里的解决方案已经扩展了bash
POSIX shell 的能力。
答案4
编辑:新答案,工作更完整:
原始失败的原因是目录名称仅添加到第一个结果中,因此当前目录中不存在的其余结果会导致错误“没有此类文件”。
一种不需要的方法find
是利用-F
选项ls
,其中包括指示 inode 类型的尾随字符。以下是一个不完整的答案,它通过以下方式从列表中删除目录grep
:更完整的答案将删除应排除的其他索引节点类型。这些sed
命令删除*
添加到可执行文件中的-F
.
source="<some directory name>"
destination='.'
someCount=5 # e.g.
while IFS=\ read -r; do
cp "${source}/${REPLY}" "${destination}"
done <<<"$(ls "${source}" -Ft | grep -v '/$' | head -5 | sed 's/\*$//')"
原答案:
假设最大的文件是一、二、三和四个。问题中的命令最终是
cp specific/directory/one two three four .
由于 . 中不存在 2、3 和 4,因此该命令失败。类似于
source=specific/directory
set -f # disable globbing
IFS='
' # split on newlines only
for file in $(ls -S $source); do
cp "${source}/${file}" .
done
会做的。
警告:如果任何文件名中有任何换行符(或者即使ls
不打印到终端也损坏文件名),这将会中断。