是否有一个简单的命令可以将远程主机目录中最近修改的文件 SCP 下来?我可以弄清楚如何从本地到远程执行此操作……例如:
scp ``ls -Art | tail -n 1\`` usr@remote:/var/log/yeet
我如何做同样的事情,但从远程到本地。(因此,从 yeet 获取最近修改的文件并将其复制到本地主机)
答案1
这里几乎没有什么问题。
解析ls
首先你不应该解析ls
。您的ls -Art | tail -n 1
存在缺陷,无法可靠地修复。标准的可靠替代方案是find
和/或使用以空字符结尾的行。
此外,我假设您需要直接在当前目录(而不是子目录中)中获取最近修改的文件(而不是目录)。
POSIX 通常不需要以空字符结尾的列表形式解析输入项的能力。如果您的工具具有足够丰富的选项,那么这是一个强大的替代品ls -Art | tail -n 1
:
find . -maxdepth 1 -type f -printf '%T@ %p\0' |
sort -zrn |
head -zn 1 |
cut -z -d " " -f 2-
它以空字符结尾的文件名产生结果。要处理它,您需要将其通过管道传输到xargs -0 …
,例如:
… | xargs -0r cp -t "/target/dir/" --
或者(如果你cp
不支持-t
):
… | xargs -0r -I {} cp -- {} "/target/dir/"
在这些命令中,有很多 POSIX 不需要的东西(因此不可移植),包括--
停止cp
解析选项,因此例如文件-R
不会触发递归而不是被复制。请注意,文件名以开头find . …
,.
因此--
可以安全地省略。我使用它只是为了指出处理时的一个良好的一般做法cp
。另一个好的做法是引用路径:文字/target/dir/
可以在没有引号的情况下工作,但在将此示例替换为您的特定目标路径后,您可能需要引号,所以我无论如何都会使用它们。
在您的(本地到远程)情况下,您将使用scp
而不是cp
。它在解析参数时可能会有自己的怪癖。
符合 POSIX 但性能不佳的命令可能是:
find . ! -name . -prune -type f -exec sh -c '
[ "$(find . ! -name . -prune -type f -newer "$1" -exec printf a \; |
wc -c)" -eq 0 ]' sh {} \; -print
它采用了这是另一个答案替代(非 POSIX)-maxdepth 1
。它会产生sh
可靠的嵌套两个find … -exec …
子句。外部find
探测所有文件并将它们逐一提供给内部find
。内部find
查找比给定文件更新的所有文件,为每个较新的文件打印一个字符 ( a
)。wc -c
计算这些字符。如果没有,则意味着给定文件是最近修改的;只有这样外部find
才会打印它。
在以下几种情况下,外部find
可能会打印多个文件:
- 有两个或多个文件具有相同的“最新”修改时间;
- 命令运行时目录中的文件被修改;
- 内部
find
无法统计某些文件。
因此,-quit
作为外部的最终操作find
将很有用(请注意,它对于内部也很有用find
,但原因不同)。不幸的是,-quit
它不是 POSIX。
我使用了-print
(-print0
不是 POSIX),但非标准文件名仍然不是问题,因为您不需要将输出通过管道传输到另一个命令。只需使用-exec
它就可以正确处理所有可能的文件名;例如,-print
您使用:
-exec yet_another_command {} \;
现在您知道如何在不解析的情况下在本地目录中找到最近修改的文件ls
。
在远程系统上查找文件
无论您选择哪种方法(包括有缺陷的方法ls … | tail …
),您都需要在远程系统上运行命令(或者不,我稍后会介绍这个异常)才能在远程目录中找到所需的文件。
最明显的方法是ssh
进入远程系统。在新的上下文中,远程系统是本地的,而您的本地计算机是远程的。(这里我使用上面的 POSIX 兼容命令作为示例,但find … | sort -z … | head -z … | cut -z … | xargs -0 …
如果只有远程系统支持所有必需的选项,您可以使用我们的第一个命令)。
ssh usr@remote
# now on the remote system
cd "/source/dir/" &&
find . ! -name . -prune -type f -exec sh -c '
[ "$(find . ! -name . -prune -type f -newer "$1" -exec printf a \; |
wc -c)" -eq 0 ]' sh {} \; -exec scp {} usr@local:"/target/dir/" \;
注意,如果你想避免cd
使用 ,find /source/dir …
那么还有更多.
-s 需要替换为/source/dir
。使用 会更容易cd
。
您需要本地系统可通过 SSH 从远程系统访问。如果途中有 NAT,您可以使用远程端口转发绕过它,如下所示:
ssh -R 12322:127.0.0.1:22 usr@remote
# now on the remote system the same cd + find as above
# only the scp part is different
… -exec scp -P 12322 {} [email protected]:"/target/dir/" \;
请注意,它会使远程端口12322
通向您的本地端口sshd
,并且远程系统的任何用户都可能试图滥用它。另一个问题是:远程系统可能配置为首先不允许您转发端口。
您可能希望在本地系统上调用单个命令。在这种情况下,需要正确引用,这会变得更加麻烦:
ssh usr@remote '
cd "/source/dir/" &&
find . ! -name . -prune -type f -exec sh -c '"'"'
[ "$(find . ! -name . -prune -type f -newer "$1" -exec printf a \; |
wc -c)" -eq 0 ]'"'"' sh {} \; -exec scp {} usr@local:"/target/dir/" \;
'
不过,如果远程scp
需要输入密码,我预计会有麻烦。出于这个原因,或者如果您无法运行/访问本地sshd
,您可能需要另一种方法。
此本地命令将打印从远程系统获取的所需文件名:
ssh usr@remote '
cd "/source/dir/" &&
find . ! -name . -prune -type f -exec sh -c '"'"'
[ "$(find . ! -name . -prune -type f -newer "$1" -exec printf a \; |
wc -c)" -eq 0 ]'"'"' sh {} \; -print
'
你可以使用它当地的xargs
和等工具scp
。请注意,解析结果-print
仅比解析 略好ls
。使用-print0
(非 POSIX,可能不可用) 或-exec printf "%s\0" {} \;
(应该有效) 代替 来-print
获取所需的文件名作为以空字符结尾的字符串。现在由您决定在本地如何处理它。如果您需要具有符合 POSIX 标准的远程命令但本地系统中的工具具有丰富的选项,这将非常有用。
值得注意的例外:sshfs
sshfs
允许您usr@remote:"/source/dir/"
作为进行挂载/local/path/
。请参阅我的这个答案,我不会重复介绍所有细节。 在您的案例中,具有丰富(不限于 POSIX)工具的(本地)过程如下:
sshfs usr@remote:"/source/dir/" "/local/path/"
find "/local/path/" -maxdepth 1 -type f -printf '%T@ %p\0' |
sort -zrn |
head -zn 1 |
cut -z -d " " -f 2- |
xargs -0r cp -t "/target/dir/" --
fusermount -u "/local/path/"
这很棒。你所做的一切都与你所做的一样当地的工具。只要你能sshfs
在远程端使用这些工具,它们的可用选项就不再重要了。你是否能从外部访问本地系统也并不重要。方法是一样的:远程到本地或本地到远程,这都无关紧要。