我正在尝试使用以下命令将新行附加到多个文件:
find -name *.ovpn -exec sh echo "line to append" >> {} \;
在执行此操作之前,我运行了不同的命令以确保它能按我预期的方式工作:
find -name *.ovpn -exec sh echo "hello" \;
但这一切只是为找到的每个文件打印出“sh:0:无法打开回显”。
答案1
问题很少。
>>
在您的第一个命令中,将被您当前的shell解释为重定向到字面上名为的文件{}
,除非它被引用。
*.ovpn
可能会在运行之前通过 shell 通配符进行扩展find
。如果当前目录中至少有一个对象与模式匹配,就会发生这种情况。您确实想引用它。比较这个问题。
你得到答案是Can't open echo
因为你确实sh
要打开echo
。要执行命令你需要sh -c
。
find
不指定路径是不可移植的(比较这个问题)。虽然您可能不会遇到这种情况,但我提到这个问题是为了让答案对其他用户更有用。
这是第一个命令的改进版本有点工作(不要运行,继续阅读):
find . -name '*.ovpn' -exec sh -c 'echo "line to append" >> "{}"' \;
请注意,我必须{}
在单引号内加双引号。这些双引号被“看到”,并使sh
带空格等的文件名作为重定向目标。如果没有引号,您最终可能会得到echo "line to append" >> foo bar.ovpn
类似于 的结果,这相当于echo "line to append" bar.ovpn >> foo
。引用可以使它变成这样echo "line to append" >> "foo bar.ovpn"
。
不幸的是包含的文件名"
会破坏这种语法。
{}
传递的正确方法sh
不是将其包含在命令字符串中,而是将其内容作为单独的参数传递:
find . -name '*.ovpn' -exec sh -c 'echo "line to append" >> "$0"' {} \;
$0
命令字符串内部扩展为我们sh
获取的第一个参数-c '…'
。现在即使"
在文件名中也不会破坏语法。
通常(比如在脚本中)指的是您使用的第一个参数$1
。这就是一些用户宁愿使用虚拟参数的原因$0
,如下所示:
find . -name '*.ovpn' -exec sh -c 'echo "line to append" >> "$1"' dummy {} \;
如果它是一个脚本,$0
将扩展为它的名称。这就是为什么看到它dummy
实际上是sh
(或bash
,如果有人调用bash -c …
等;检查此链接)。 像这样:
find . -name '*.ovpn' -exec sh -c 'echo "line to append" >> "$1"' sh {} \;
但是等等!为每个文件find
调用一个单独的进程sh
。我不希望您有数千个.ovpn
文件,但一般来说,您可能希望处理许多文件而不产生不必要的进程。我们可以优化方法,以便tee -a
可以作为单个进程写入多个文件:
find . -name '*.ovpn' -exec sh -c 'echo "line to append" | tee -a "$@" >/dev/null' sh {} +
注意{} +
,这会一次传递多个路径。在执行的命令中,sh -c
我们使用 检索它们"$@"
,这会扩展为。在这种情况下,必须有"$1" "$2" "$3" …
一个填充 (未使用) 的伪参数。$0
一般来说还存在这个问题:为什么printf
比 更好echo
?但是在这种情况下,您使用的echo
没有选项,并且它获取的字符串是静态的,所以应该没问题。
答案2
我的脚本将多行保存到.csv
文件中:
#! /bin/bash
while [ 1 ]
do
sleep 1s
str="\""$(date)"\""
echo ${str} >>333.csv
# begin add next row
echo ",\"" >>333.csv
# append multiline result
ps >>333.csv
# end add next row
echo "\"" >>333.csv
# begin add next row
echo ",\"" >>333.csv
# append multiline result
df -h >>333.csv
# end add next row
echo "\"" >>333.csv
sed -i ":a;N;s/\"\n\,/\"\,/g;ta" ./333.csv
done