Windows批处理:不换行的打印echo

291

如何在Windows批处理文件中实现Linux shell命令echo -n的效果,这个命令可以去掉输出内容末尾的换行符号?

目的是为了在循环中将内容写入同一行。


3
请参见 [https://dev59.com/znVC5IYBdhLWcg3wlyMo#374361][1]。 - Patrick Cuff
2
这让我快要疯了...我知道 echo 可以使用 -n,但在 cmd.exe 上它就是不起作用^^ - panny
15
尽管名称相同,但 Windows 和 Posix 中的 echo 命令并不相同。与 nslookup 一样,它也有不同的选项。因此,在与 Windows 相关的问题中,“我知道 echo 可以使用 -n”这条评论是不正确的。 - user66001
@user66001 - 在Windows上使用"echoX -n"的详细信息已在下面的答案中发布。 - Bilbo
@Bilbo - 不确定你使用批处理的方式,但99%的时间我都是用它来帮助自动化别人的事情,而不是在我的设备上。如果要提供一个批处理文件给第三方工具,这并不容易或高效(特别是如果这个第三方工具是.exe类型的,考虑到病毒环境已经过去了7年,相对于我最初的评论)。 - user66001
20个回答

314

使用set/p参数,您可以在不换行的情况下输出:

C:\> echo Hello World
Hello World

C:\> echo|set /p="Hello World"
Hello World
C:\>

源代码


107
由于创建了两个额外的 cmd 任务,该管道非常缓慢。更快的方法是使用 <nul set /p =Helloset /p 无法输出等号作为第一个字符,也无法输出空格/制表符。 - jeb
16
在我的系统上,速度快了近15倍。 - jeb
32
在“Hello World”两侧加上引号,以防止在字母d后面出现多余的空格。 - Brian
18
警告:这将会改变 ERRORLEVEL 值为 1。请参考 @xmechanix 的回答。 - CoperNick
8
@user246694 < nul 是从 NUL 设备进行的重定向。它不包含任何内容,但对于 set /p 来说已经足够停止等待用户输入了。 - jeb
显示剩余9条评论

140

使用echo | set /p=或者<NUL set /p=都可以用来阻止换行。

然而,当编写更高级的脚本并且检查ERRORLEVEL变得重要时,未指定变量名设置set /p=将会使程序变得十分危险,因为这会将ERRORLEVEL设置为1。

一个更好的方法是只需像下面这样使用虚拟变量名:
echo | set /p dummyName=Hello World

这将完全产生您想要的结果,没有任何背景上的诡异事情发生,正如我不得不通过艰难的方式找出的那样,但这仅适用于管道版本;<NUL set /p dummyName=Hello依旧会将ERRORLEVEL提高到1。


32
警告:该命令将把ERRORLEVEL设置为0。如果ERRORLEVEL大于0,则将其更改为0。这也可能在编写更高级脚本时存在危险。(但回答得好加1分) - CoperNick
2
所以基本上你需要一个 IF ERRORLEVEL==0 (...) ELSE (...) 来避免在这些情况下损害你的环境。天哪。 - SilverbackNet
2
写更高级的批处理脚本本身就很危险 :) 即使不考虑稳定性,至少对编写者的心理状态也是有风险的。 - gilad905
我不确定“非常危险”是否是正确的短语,因为它实际上取决于脚本的用途和潜在后果。也许可以说“这可能会导致意外行为”或其他类似的表述。 - jordanbtucker

33
简单的SET /P方法在不同版本的Windows中有一些限制。
  • 前导引号可能会被剥离
  • 前导空格可能会被剥离
  • 前导=会导致语法错误。
请参阅http://www.dostips.com/forum/viewtopic.php?f=3&t=4209获取更多信息。
jeb发布了一个聪明的解决方案,可以解决大部分问题,网址是Output text without linefeed, even with leading space or =。我改进了这种方法,使其可以在任何版本的Windows上安全地打印绝对任何有效的批处理字符串而不带换行符。请注意,:writeInitialize方法包含一个字符串文字,可能无法发布到该站点。备注说明了应该是什么字符序列。 < p > < code >:write 和 :writeVar 方法做了优化处理,只有包含前导问题字符的字符串才使用我的修改版 jeb 的 COPY 方法进行写入。非问题字符串会使用更简单且更快的 SET /P 方法进行写入。

@echo off
setlocal disableDelayedExpansion
call :writeInitialize
call :write "=hello"
call :write " world!%$write.sub%OK!"
echo(
setlocal enableDelayedExpansion
set lf=^


set "str= hello!lf!world^!!!$write.sub!hello!lf!world"
echo(
echo str=!str!
echo(
call :write "str="
call :writeVar str
echo(
exit /b

:write  Str
::
:: Write the literal string Str to stdout without a terminating
:: carriage return or line feed. Enclosing quotes are stripped.
::
:: This routine works by calling :writeVar
::
setlocal disableDelayedExpansion
set "str=%~1"
call :writeVar str
exit /b


:writeVar  StrVar
::
:: Writes the value of variable StrVar to stdout without a terminating
:: carriage return or line feed.
::
:: The routine relies on variables defined by :writeInitialize. If the
:: variables are not yet defined, then it calls :writeInitialize to
:: temporarily define them. Performance can be improved by explicitly
:: calling :writeInitialize once before the first call to :writeVar
::
if not defined %~1 exit /b
setlocal enableDelayedExpansion
if not defined $write.sub call :writeInitialize
set $write.special=1
if "!%~1:~0,1!" equ "^!" set "$write.special="
for /f delims^=^ eol^= %%A in ("!%~1:~0,1!") do (
  if "%%A" neq "=" if "!$write.problemChars:%%A=!" equ "!$write.problemChars!" set "$write.special="
)
if not defined $write.special (
  <nul set /p "=!%~1!"
  exit /b
)
>"%$write.temp%_1.txt" (echo !str!!$write.sub!)
copy "%$write.temp%_1.txt" /a "%$write.temp%_2.txt" /b >nul
type "%$write.temp%_2.txt"
del "%$write.temp%_1.txt" "%$write.temp%_2.txt"
set "str2=!str:*%$write.sub%=%$write.sub%!"
if "!str2!" neq "!str!" <nul set /p "=!str2!"
exit /b


:writeInitialize
::
:: Defines 3 variables needed by the :write and :writeVar routines
::
::   $write.temp - specifies a base path for temporary files
::
::   $write.sub  - contains the SUB character, also known as <CTRL-Z> or 0x1A
::
::   $write.problemChars - list of characters that cause problems for SET /P
::      <carriageReturn> <formFeed> <space> <tab> <0xFF> <equal> <quote>
::      Note that <lineFeed> and <equal> also causes problems, but are handled elsewhere
::
set "$write.temp=%temp%\writeTemp%random%"
copy nul "%$write.temp%.txt" /a >nul
for /f "usebackq" %%A in ("%$write.temp%.txt") do set "$write.sub=%%A"
del "%$write.temp%.txt"
for /f %%A in ('copy /z "%~f0" nul') do for /f %%B in ('cls') do (
  set "$write.problemChars=%%A%%B    ""
  REM the characters after %%B above should be <space> <tab> <0xFF>
)
exit /b

2
很高兴看到一个非常强大的解决方案,可以处理所有字符。 - jeb
3
我想指出的是,如果计算机恰好与互联网连接,那么大约可以使用这么多批处理代码来下载和安装Python或其他可以帮助你摆脱批处理的东西。批处理很有趣,但它最适合用于周末挑战的字符串功能。 - n611x007
@dbenham 在第6行使用的 "echo(" 和 "echo." 功能上是一样的吗?我之前没见过这种写法。 - Skip R
2
@SkipR - 是的,它在功能上与ECHO.相同,除了在模糊情况下可能会失败。还有一堆其他形式也可能在模糊情况下失败。唯一始终有效的是ECHO( - dbenham
@dbenham: ECHO( 也失败了,它返回错误级别9009(参见:Windows 11上cmd.exe %errorlevel% 9009的官方MS参考)。 - Luuk

12
作为对 @xmechanix 回答的补充,我通过将内容写入文件发现了以下情况:
echo | set /p dummyName=Hello World > somefile.txt

这将在打印的字符串末尾添加一个额外的空格,这可能会不方便,特别是因为我们试图避免在字符串末尾添加新行(另一个空白字符)。

幸运的是,引用要打印的字符串,即使用:

echo | set /p dummyName="Hello World" > somefile.txt

将不带任何换行符或空格字符的字符串打印出来。


6
如果你在World>之间没有放置空格,那么额外的空格应该消失。 - aschipfl
1
@aschipfl 尝试过了,不行。 - Joan Bruguera
2
回显 | set /p "dummyName=Hello World" > somefile.txt - Ben Personick
echo | set /p "=Hello World" >somefile.txt - Ben Personick
3
如果您不使用引号并使用管道符,则会出现额外的空格。xxd是一个十六进制转储实用程序,所以我们可以看到例如set /p="abc"<nul|xxd -p显示616263,而set /p=abc<nul|xxd -p则给出61626320。请注意,本文仅涉及翻译,不包含解释或其他内容。 - barlop
显示剩余3条评论

11

解决SET /P中被剥离的空格问题:

关键在于回退字符,您可以在DOS文本编辑器EDIT中调用它。要在EDIT中创建它,请按ctrlP+ctrlH。我想在这里粘贴它,但是这个网页无法显示它。不过在记事本上可见(它很奇怪,像是一个带有白色圆圈的小黑色矩形)。

因此,您可以编写以下内容:

<nul set /p=.9    Hello everyone

点可以是任意字符,它只是告诉 SET /P 文本从那里开始,即在空格之前,而不是在“Hello”处开始。 “9”是退格符的表示形式,我无法在此处显示。您必须将其替换为 9,然后它将删除“.”,之后您将得到这个:

    Hello Everyone

而不是:

Hello Everyone

我希望这对你有所帮助


2
我忘了告诉你,它在Windows 7上可以运行。 它不能在命令行中工作,只能在批处理中使用。对于任何没有得到字符的人,请下载此示例:http://pecm.com.sapo.pt/SET_with_backspace_char.txt - Pedro
同样适用于Echo。 - Ben Personick
所以,在进行了非常彻底的搜索后,我在网络上没有找到这个东西。这可能是我第一次偶然发现了什么东西。对于那些不知道的人,如果您将cmd提示符置于传统模式并使用正确的字体,则可以输入控制字符。但是,如果您只是将任何内容重定向到文件或>con,则也可以工作。认真地将以下内容粘贴到命令提示符中:“echo ←c◙◙○○ Harharhar!!◙○○ -------------◙◙○○ I am the worst!?:'(○ ♪○NOPE!•♪○ I feel great! Hazzah-le-oop!◙◙○ I am programmeer hear me ... something.◙>con”。 - Brent Rittenhouse
好的,我希望在其他人之前发现这个问题,但是...这似乎取决于不在Unicode代码页中。您可以将起始代码存储在变量中,执行“chcp 437”命令并将其设置为变量,或输出它,然后切换回来(它应该仍然输出)。我还在试验中。 - Brent Rittenhouse
此外,仅使用退格键即可,它可以在没有额外字符(如句点)的情况下正常工作。因此,接收方应在需要处理字符串时删除前导退格符,否则在直接打印时,接收方应注意当没有句点时,退格符将向左删除一个字符。 - subcoder
显示剩余4条评论

8

这里介绍另一种方法,它使用Powershell Write-Host命令,该命令带有一个-NoNewLine参数,将其与start /b结合使用,就可以从批处理中实现相同的功能。

NoNewLines.cmd

@ECHO OFF
start /b /wait powershell.exe -command "Write-Host -NoNewLine 'Result 1 - ';Write-Host -NoNewLine 'Result 2 - ';Write-Host -NoNewLine 'Result 3 - '"
PAUSE

输出

Result 1 - Result 2 - Result 3 - Press any key to continue . . .

以下内容略有不同,与原始问题的需求不完全相符,但很有趣,因为每个结果都会覆盖前一个结果,模拟计数器功能。
@ECHO OFF
start /b /wait powershell.exe -command "Write-Host -NoNewLine 'Result 1 - '"
start /b /wait powershell.exe -command "Write-Host -NoNewLine 'Result 2 - '"
start /b /wait powershell.exe -command "Write-Host -NoNewLine 'Result 3 - '"
start /b /wait powershell.exe -command "Write-Host -NoNewLine 'Result 4 - '"
start /b /wait powershell.exe -command "Write-Host -NoNewLine 'Result 5 - '"
start /b /wait powershell.exe -command "Write-Host -NoNewLine 'Result 6 - '"
start /b /wait powershell.exe -command "Write-Host -NoNewLine 'Result 7 - '"
start /b /wait powershell.exe -command "Write-Host -NoNewLine 'Result 8 - '"
start /b /wait powershell.exe -command "Write-Host -NoNewLine 'Result 9 - '"
PAUSE

7

我根据@arnep的想法制作了一个函数:

echo|set /p="你好,世界"

这就是函数内容:

:SL (sameline)
echo|set /p=%1
exit /b

使用 call :SL "Hello There" 命令来调用。
我知道这没什么特别的,但我想了很久才想到它,所以我在这里发布了它。


7

你可以使用gnuwin32(coreutils软件包)中的“tr”命令来删除换行符。

@echo off
set L=First line
echo %L% | tr -d "\r\n"
echo Second line
pause

顺便提一下,如果你要做很多脚本编程,gnuwin32 这个工具包是个宝藏。

我还建议安装git bash。虽然它与gnuwin32不同(因此是一座金矿+1),但它只是一个很好的设置,你几乎可以在任何地方包括开始菜单按钮上使用git-bash。 - hakre
你能否使用tr仅删除最后一个空格字符,就像“trim”一样? - panny
使用sed进行修剪:sed -e "s/ *$//" - BearCode
@panny - 我找不到名为trim的posix/linux工具。此外,\r\n\r\n(分别是OSX、Posix和Windows中的换行符)都不是“空格”字符。 - user66001
1
如果您已安装gnuwin32(或git),则可以使用 printf 代替 echoprintf 默认不会发出换行符。 - Ross Smith II

4

DIY cw.exe (控制台写入)实用工具

如果你找不到现成的、可用的,那么就 DYI 吧。使用此 cw 实用工具,您可以使用各种字符。至少我是这样认为的,请进行压力测试并让我知道。

工具

您只需要安装.NET,这在今天是非常普遍的。

材料

一些输入/复制粘贴的字符。

步骤

  1. 创建包含以下内容的 .bat 文件。
/* >nul 2>&1

@echo off
setlocal

set exe=cw
for /f "tokens=* delims=" %%v in ('dir /b /s /a:-d  /o:-n "%SystemRoot%\Microsoft.NET\Framework\*csc.exe"') do set "csc=%%v"

"%csc%" -nologo -out:"%exe%.exe" "%~f0"

endlocal
exit /b %errorlevel%

*/

using System;

namespace cw {
    class Program {
        static void Main() {
            var exe = Environment.GetCommandLineArgs()[0];
            var rawCmd = Environment.CommandLine;
            var line = rawCmd.Remove(rawCmd.IndexOf(exe),exe.Length).TrimStart('"');
            line = line.Length < 2 ? "\r" : line.Substring(2) ;
            Console.Write(line);
        }
    }
}
  1. 运行它。

  2. 现在你拥有了一个漂亮的4KB实用程序,因此您可以删除.bat文件。

或者,您可以将此代码作为子例程插入到任何批处理中,将生成的.exe发送到%temp%,在批处理中使用它,并在完成后将其删除。

如何使用

如果您想写一些不带换行符的内容:
cw 无论您想要什么,甚至是带有"",但请记住转义^|、^^、^&等,除非像"| ^ &"中那样双引号引用。

如果您想进行回车(回到行的开头),只需运行
cw

所以从命令行尝试这个:

for /l %a in (1,1,1000) do @(cw ^|&cw&cw /&cw&cw -&cw&cw \&cw)

我受到你的回答的启发,使用Go创建了这个小工具,并为Azure Pipelines创建了这个附带任务。它只是获取输入的stdout并以特定格式输出,以便构建程序可以捕获。谢谢! - riezebosch

3

From here

<nul set /p =Testing testing

同时,要想在首部添加空格来回显内容,请使用以下方法:

echo.Message goes here

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