将`echo`命令的输出通过管道传输给netcat失败,而将`printf`命令的输出通过管道传输则成功。

5
我正在使用Linux Ubuntu 18.04和20.04上的netcat (nc)通过TCP以太网向Teledyne LeCroy T3PS3000电源设备发送命令。如果我使用echo,设备无法正确接收命令,但如果我使用printf,则可以正常工作。为什么?
# Example command to Ethernet-connected digital power supply over TCP

# fails
echo 'measure:voltage? ch1' | timeout 0.2 nc 192.168.0.1 5025

# works
printf 'measure:voltage? ch1' | timeout 0.2 nc 192.168.0.1 5025

参考文献:

  1. timeout cmd: https://unix.stackexchange.com/questions/492766/usage-of-nc-with-timeouts-in-ms/492796#492796
    1. Update/Note (written after I wrote the answer, so the printf form below is correct): without the timeout command, we would have to use the -w option with netcat, but it accepts only whole integer wait period timeouts in seconds, like this (notice the -w 1 to set the 'w'ait period, or timeout, to 1 whole second):
      printf '%s' "my command to send" | nc -w1 192.168.0.1 5025 
      
  2. Teledyne LeCroy T3PS3000 power supply: from my git & Linux cmds, help, tips & tricks - Gabriel.txt document in my eRCaGuy_dotfiles repo:
    1. For help, contact:
      Teledyne LeCroy Support
      support@teledynelecroy.com
      700 Chestnut Ridge Road
      Chestnut Ridge, NY 10977
      (800) 553-2769 Option 3
    2. It's not in the manual, so I emailed them and they told me:

      The T3PS3000 uses socket communication and the default port is 5025.

    3. Manual (Quick Start Guide): http://cdn.teledynelecroy.com/files/manuals/t3ps3000-quick-start-guide.pdf
      • p24, "Chapter 3 Remote Control" is where the command interface begins.
2个回答

7

啊!找到了。

确保你不是在命令的末尾意外发送了一个换行符(\n

看起来 echo 在字符串末尾添加了一个换行符,而 printf 则没有。这个末尾的换行符干扰了设备解析命令的能力。如果我强制在 printf 命令的末尾添加它(一个换行符,\n),那么它也会失败——这意味着设备不会按预期响应该命令:

# fails:
printf 'measure:voltage? ch1\n' | timeout 0.2 nc 192.168.0.1 9999

...而且看起来你可以通过使用-n来抑制echo的尾随换行符:
# works!
echo -n 'measure:voltage? ch1' | timeout 0.2 nc 192.168.0.1 9999

# also works, of course, as stated in the question
printf 'measure:voltage? ch1' | timeout 0.2 nc 192.168.0.1 9999

man echo中:

-n     do not output the trailing newline
关键要点:使用printfecho -n来避免在打印结束时有一个尾随的换行符。
但是,这还不是全部!以下是另外两个要点:
1. 如果您希望脚本具有可移植性和预期行为,并且能够向设备发送任何命令,请完全不要使用echo

使用echo总体而言并不是一个好主意,当试图使用它发送任何可能的字符串到设备上时!为什么呢?@Jeff Schaller在评论中指出了以下资源给我参考点击这里,其中包含了非常有价值的信息:大量的关于为什么printfecho更好的相关内容:Unix & Linux: Why is printf better than echo?

不使用echo的几个关键原因包括:

  1. 它的实现、参数和行为都有些不太正规。

  2. 它不具备可移植性:在不同系统上,它有各种不同的实现方式。有些支持-e-n,有些则不支持。有些默认情况下就具备了-e的行为,而无需-e标志。等等。

  3. 在某些shell中,它根本无法将-n作为命令打印出来。请参见我在这里的评论

    在Ubuntu 18.04和20.04上的bash中,echo "-n"应该输出-n。但实际上,它什么也不输出,因为它将其视为与-n标志相同的内容。echo -- "-n"应该解决这个问题并输出-n,但事实并非如此。它输出的是-- -n。我现在非常明白你的观点了。使用printf更好。

  4. 正如@Charles Duffy评论中指出的那样:即使是echo的POSIX规范也建议不要使用echo,因为存在许多这些不一致和原因:

    在所有POSIX系统上都无法可移植地使用echo...

    鼓励新应用程序使用printf而不是echo

FYI,这里有一些关键引用来自Unix & Linux: Why is printf better than echo?
关于echo
"……确保$var不包含反斜杠字符,并且不以-开头"
关于printf
"但是请记住,第一个参数是格式,因此不应包含变量/不可控数据"
2. 使用printf正确地作为printf '%s' "my command string",而不是printf "my command string" 在下面的评论中,可以看到更多的讨论,我和@Charles Duffy之间。你应该像这样使用printf
# CORRECT USAGE: >>> FINAL ANSWER; DO THIS! <<<
printf '%s' 'measure:voltage? ch1' | timeout 0.2 nc 192.168.0.1 9999  

# NOT this:
printf 'measure:voltage? ch1' | timeout 0.2 nc 192.168.0.1 9999

这是因为传递给 printf 的第一个参数被解析为格式字符串。例如,如果你尝试发送到设备的命令字符串是 %s,那么这不会发送任何内容!
printf "%s"

输出为空,因为printf将第一个参数解释为格式字符串,而%s在格式字符串中有特殊含义。但是这会将%s作为字面值发送出去。
printf "%s" "%s"

输出是
%s

在调用printf函数时,参数中的第一个"%s"格式字符串,而第二个"s"是要替换到格式字符串中第一个%s位置的字符串字面值,这符合printf函数的规范。

这意味着正确使用printf函数可以向设备发送任何命令,而错误的使用会剥离掉任何格式化字符,如%s%02X%u等等--即任何printf风格的格式字符串特殊字符或序列

另请参阅:

  1. Unix & Linux:为什么printf比echo更好?
  2. 关于echo的POSIX规范
  3. 我的netcat教程:ServerFault:包括设置接收时要绑定的IP地址,或发送时要发送到的IP地址的一般netcat(nc)使用说明

2
为了更深入地了解此问题(请注意,echo 的各种实现和“因此,您不能使用 echo 显示不受控制的数据”),请参阅以下链接:https://unix.stackexchange.com/questions/65803/why-is-printf-better-than-echo - Jeff Schaller
1
“echo -n” 是不可靠的——即使是 POSIX 规范中的 echo 也明确指出了这一点。(上面的 [unix.se] 链接提供了更详细的信息)。 - Charles Duffy
4
顺便提一句,使用 printf '%s' "string" 比使用 printf "string" 更加可靠,因为前者将字符串强制视为文字而非格式化字符串。 - Charles Duffy
1
否则,如果您的字符串包含%、反斜杠或其他在格式化字符串中具有特殊意义的字符,则会以与原始输入不匹配的格式进行输出。 - Charles Duffy
1
哦,对了。我明白了。如果我要发送的字符串是%s,例如,这不会发送任何东西:printf "%s"。输出为空,因为printf将第一个参数解释为格式字符串,而%s在格式字符串中有特殊意义。但是这确实将我的%s作为文字发送:printf“%s”“%s”。 输出为%s。我会在我的答案中更新这个新信息。 - Gabriel Staples
显示剩余3条评论

0

看起来在Ubuntu 18.04上netcat发生了变化,你可以使用"netcat -N"来完成交易。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接