如何从文件中读取一些行并将更改和添加的字符输出到另一个文件中?

How to read some lines from a file and output them with changed and added characters into another file?

我为我的开发环境变量写了一个批处理脚本,但由于我不熟悉 Windows 批处理脚本,所以遇到了一些麻烦。

这是我到目前为止尝试过的:

echo window._env_ = { > ./env-config.js
findstr /v # .env >> ./env-config.js
echo } >> ./env-config.js

它输出这个:

window._env_ = { 
REACT_APP_SITE_TITLE=MyWebsite
REACT_APP_SITE_URL=http://localhost:3000
BROWSER=none
REACT_APP_API_ENDPOINT=http://localhost:5000
} 

这些是存储在文件.env中的环境变量,我想写入文件env-config.js:

#
# Website Title
REACT_APP_SITE_TITLE=MyWebsite
#
# Website URL
REACT_APP_SITE_URL=https://my-website-url.org
#
# Define if the Browser auto-open at react start
BROWSER=none
#
# Define the API Endpoint for the Backend
REACT_APP_API_ENDPOINT=http://localhost:5000

最后的 env-config.js 应该是这样的:

window._env_ = {
REACT_APP_SITE_TITLE: "MyWebsite",
REACT_APP_SITE_URL: "https://my-website-url.org",
BROWSER: "none",
REACT_APP_API_ENDPOINT: "http://localhost:5000",
}

所以我需要做的是在引号中设置输出中的变量值,将=更改为:并在末尾添加一个逗号。但我找不到结合 findstr.

的解决方案

有没有人知道如何从输入中获得想要的输出?

FINDSTR 使用选项 /V all lines NOT 分别输出包含搜索字符串或正则表达式匹配字符串的所有行 包含搜索字符串或正则表达式匹配的字符串(反转结果)。因此 FINDSTR 可以用来过滤掉以 # 开头的行,但其他行是存储在文件 .env 中的输出,这在这里没有帮助因为不以 # 开头的行应该重新格式化写入文件 env-config.js.

解决方案是使用 FOR /F 循环来处理文本文件中的每一行 .env 并输出不以 # 开头的行命令 ECHO 将感兴趣的值根据需要重新格式化并重定向到文件 env-config.js.

只有一个命令行的批处理文件解决方案:

@(echo window._env_ = {& (for /F "eol=# tokens=1* delims==" %%I in (.env) do @echo %%I: "%%J",) & echo })> .\env-config.js

@ 在命令行的开头只执行一次(第一个 @ 之后的所有内容)或多次(echo %%I: "%%J", 在循环内)在批处理文件执行期间阻止输出解析之后和执行之前的命令行。批处理文件通常在顶部包含 @echo off 以关闭批处理文件中所有其他命令行的命令回显模式,并防止第一个命令行的输出。

第一个 @ 之后的左圆括号 ( 定义了一个命令块,它以最后一个右括号 ) 结束,留给重定向运算符 >。处理批处理文件的 Windows 命令处理器 cmd.exe 首先打开文件 env-config.js 当前目录 中定义的相对路径 .\ 用于写入操作,如果该文件已经存在,则将文件截断为零长度,因为在执行命令块期间处理 STDOUT(标准输出)的所有输出都应重定向到该文件中。该文件将保持打开状态,直到命令块内的所有命令执行完毕。

双引号参数字符串外的 & 符号 & 被解释为 无条件 AND 命令运算符。有关无条件和条件(&&||)命令运算符的更多详细信息,请参阅 single line with multiple commands using Windows batch file.

单个命令行上的命令块包含此处执行一次的三个命令。

ECHO 与大多数其他 Windows commands 不同,因为它不将 space 字符解释为参数字符串分隔符,而是解释为要输出的字符。因此,{& 之间没有 space,以避免 space 由 ECHO 输出,导致尾随space 文件 env-config.js.

通过将单个命令行版本拆分为多个命令行,可以更容易地看到执行了哪些命令。

@echo off
(
    echo window._env_ = {
    for /F "eol=# tokens=1* delims==" %%I in (.env) do echo %%I: "%%J",
    echo }
)> .\env-config.js

在这种情况下不再需要由 ( 定义到 for 并在 , 之后以 ) 结尾的内部命令块,因为行终止定义其中 FOR 命令行执行多次命令 ECHO 结束。在单命令行版本中,需要两个圆括号让 cmd 知道在每个循环迭代中由 FOR 执行的命令行在哪里结束。

首先在打开的文件env-config.js中写入命令ECHO输出的字符串window._env_ = {,回车return和line-feed 作为行终止符 (18 characters/bytes).

接下来当前目录中的文件.env被打开进行读操作,因为命令FOR[=220的选项/F =] 和文件名 .env 既没有包含在 "(字符串处理)中,也没有包含在 '(命令行捕获输出的处理)中。

FOR /F 处理文本文件行的默认行为是

  • 忽略空行,即不包含任何字符而不是行结束字符回车 return 和 line-feed、
  • 使用普通 space 和水平制表符作为字符串分隔符将行拆分为子字符串,并从行中删除所有前导 spaces/tabs,
  • 查看第一个子字符串的第一个字符是否为分号,分号是默认的行尾字符,在这种情况下不会进一步处理该行,
  • 将不以 ; 开头的第一个 space/tab 分隔子字符串分配给指定的循环变量和
  • 分别执行do后指定命令块中的命令。

此行拆分行为已使用 " 中指定的选项 eol=# tokens=1* delims== 进行修改,以解释为选项 /F

之后的一个参数字符串
  • 根据 delims==
  • 定义的等号拆分每个 non-empty 行
  • 忽略所有以字符 # 开头的行,如 eol=#
  • 所定义
  • 据此 tw需要子字符串,第一个子字符串和第一个子字符串之后等号之后的所有内容保持原样,而不按照 tokens=1*
  • 定义的等号进一步拆分这部分行
  • 将第一个等号分隔的子字符串分配给指定的循环变量I,如果行中有更多内容,则根据ASCII table分配给下一个循环变量J

因此,文件 .env 中以 # 开头的行将被忽略,而在其他行中,第一个(系列)= 左边的字符串将分配给循环变量 I(名称)和第一个(系列)= 之后的所有内容都分配给循环变量 J(值),如果第一个等号之后没有任何内容,它也可以是一个空字符串。

这两个字符串是用命令 ECHO 输出的,在名称和值之间有一个冒号和一个 space 并且用双引号将值括起来,并在行尾,此输出使用回车 return 和 line-feed 写入打开的文件 env-config.js.

.env 的所有行都被 FOR /F 处理并关闭此文件后,输出 ECHO 字符串 } 带有换行符 return 和 line-feed.

命令块的所有命令的执行都已完成 echo } 因此 cmd 关闭文件 env-config.js 现在包含所有输出 characters/bytes.

当前目录不一定是包含批处理文件的目录。因此,如果希望文件 .env 在批处理文件的目录中,并且文件 env-config.js 也应该在批处理文件的目录中创建,最好使用以下单个命令行批处理文件。

@(echo window._env_ = {& (for /F "usebackq eol=# tokens=1* delims==" %%I in ("%~dp0.env") do @echo %%I: "%%J",) & echo })> "%~dp0env-config.js"

multi-line版本是:

@echo off
(
    echo window._env_ = {
    for /F "usebackq eol=# tokens=1* delims==" %%I in ("%~dp0.env") do echo %%I: "%%J",
    echo }
)> "%~dp0env-config.js"

%~dp0(批处理文件参数 0 的驱动器和路径)扩展为始终以反斜杠结尾的批处理文件的完整目录路径,因此无需再 \ 与文件名连接.envenv-config.js.

另外使用 FOR /F 选项 usebackq 获取包含在批处理文件目录中的文件 .env 的完全限定文件名" 解释为要处理的文本文件的文件名,而不是要处理的字符串。

请注意 Windows 上的目录分隔符是 \,而不是 Linux/Mac 上的 /,如 Microsoft 在有关 Naming Files, Paths, and Namespaces. The usage of / instead of \ in file/folder strings usually work thanks to the automatic correction applied by the Windows I/O functions as explained by the referenced Microsoft documentation page. But there are use cases on which the usage of / in a file/folder string results in unexpected behavior as described with two examples in the answer on How can I check if either or file exists in IF Exist? 的文档中所述

为了完整性,一个 multi-line 批处理文件将批处理文件目录中文件 .env 中的行输出到批处理文件目录中的文件 env-config.js 中,没有逗号最后一行来自 .env.

@echo off
setlocal EnableExtensions DisableDelayedExpansion
set "FirstLine=1"
(
    echo window._env_ = {
    for /F "usebackq eol=# tokens=1* delims==" %%I in ("%~dp0.env") do (
        if not defined FirstLine (echo ,) else set "FirstLine="
        set /P =%%I: "%%J"<nul
    )
    echo(
    echo }
)> "%~dp0env-config.js"
endlocal

两个都可以去掉%~dp0使用当前目录而不是批处理文件目录

如果当前处理的行不是从文件 .env.

读取数据的第一行

这里使用带选项/P的命令SET,不带环境变量名输出格式化后的数据处理STDOUT 没有回车 return 和 line-feed 不等待用户输入,因为设备 NUL 被用作 STDIN(标准输入)无法打开阅读内容。

额外的命令行 echo( 导致只是回车 return 和 line-feed 的输出也终止了从 .env 读取的数据的最后一行没有逗号结尾。

这些是 Microsoft 未正式记录的特殊“技巧”,但在 Stack Overflow 和 DosTips 论坛主题的许多答案中都有记录ECHO. FAILS to give text or blank line - Instead use ECHO/

为了了解使用的命令及其工作原理,请打开 command prompt window,在那里执行以下命令,并仔细阅读每个命令显示的所有帮助页面。

  • call /? ... 解释批处理文件参数引用,如 %~dp0
  • echo /?
  • endlocal /?
  • for /?
  • if /?
  • set /?
  • setlocal /?

另请参阅有关 Using command redirection operators 的 Microsoft 文档。