Home > Enterprise >  How to read some lines from a file and output them with changed and added characters into another fi
How to read some lines from a file and output them with changed and added characters into another fi

Time:04-08

I wrote a batch script for my development environment variables, but have some troubles because I'm not familiar with Windows batch scripts.

This is what I tried so far:

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

It outputs this:

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

These are the environment variables stored in the file .env which I want to write into the file 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

The final env-config.js should look like this:

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",
}

So what I need to do is to set the variable values in the output in quotes, change = to : and add a comma at the end. But I can´t find a solution combined with findstr.

Does anyone have an idea how to get the wanted output from the input?

CodePudding user response:

FINDSTR outputs all lines containing a searched string or a string matched by a regular expression respectively with the usage of option /V all lines NOT containing a searched string or a string matched by a regular expression (inverted result). Therefore FINDSTR can be used to filter out the lines starting with #, but the other lines are output as stored in the file .env which is of no help here as the lines not starting with # should be written reformatted into the file env-config.js.

The solution is using a FOR /F loop to process each line in the text file .env and output the lines not starting with # with command ECHO with the values of interest reformatted as required redirected into the file env-config.js.

The batch file solution with just a single command line:

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

@ at beginning of a command line executed just once (everything after first @) or multiple times (echo %%I: "%%J", inside the loop) during the batch file execution prevents the output of the command line after parsing and before executing it. Batch files contain usually at top @echo off to turn off command echo mode for all further command lines in the batch file and prevent also the output of this first command line.

The opening round bracket ( after first @ defines a command block which ends with the last closing parenthesis ) left to redirection operator >. The Windows Command Processor cmd.exe processing the batch file opens first the file env-config.js in the current directory as defined with the relative path .\ for write operations and truncates the file to zero length if that file exists already because of everything output to handle STDOUT (standard output) during execution of the command block should be redirected into this file. The file is kept open until the execution of all commands inside the command block finished.

The ampersand & outside a double quoted argument string is interpreted as unconditional AND command operator. For more details about unconditional and conditional (&& and ||) command operators see single line with multiple commands using Windows batch file.

The command block on a single command line contains here three commands which are executed once.

ECHO is different to most other Windows commands as it does not interpret a space character as argument string separator, but as character to output. For that reason there is no space between { and & to avoid that this space is output by ECHO resulting in a trailing space in the file env-config.js.

Which commands are executed can be seen easier by splitting the single command line version up into multiple command lines.

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

The inner command block defined by ( left to for and ending with ) after , is not needed anymore in this case because of the line termination defines where the FOR command line executing multiple times the command ECHO ends. In the single command line version the two round brackets are needed to let cmd know where the command line to execute by FOR on each loop iteration ends.

There is first written into the opened file env-config.js the string window._env_ = { output by command ECHO with carriage return and line-feed as line termination (18 characters/bytes).

Next the file .env in current directory is opened for read operations because of option /F of command FOR and the file name .env neither enclosed in " (processing of string) nor in ' (processing of captured output of a command line).

The default behavior of FOR /F on processing the lines of a text file is

  • ignoring empty lines which are lines not containing any character than the line ending characters carriage return and line-feed,
  • splitting the line up into substrings using normal space and horizontal tab as string delimiters with removal of all leading spaces/tabs from the line,
  • looking on first character of first substring on being a semicolon which is the default end of line character in which case the line is not further processed,
  • assigning the first space/tab delimited substring not starting with ; to the specified loop variable and
  • executing the command respectively the commands in the command block specified after do.

This line splitting behavior is modified with the options eol=# tokens=1* delims== specified within " to be interpreted as one argument string after option /F to

  • split up each non-empty line on equal signs as defined with delims==,
  • ignore all lines starting with the character # as defined with eol=#,
  • whereby two substrings are wanted, the first substring and everything after the equal sign(s) after first substring kept as is without splitting this part of the line up further on equal signs as defined with tokens=1* and
  • assign the first equal sign delimited substring to specified loop variable I and if there is something more on the line to the next loop variable J according to ASCII table.

So the lines starting with # in file .env are ignored and on other lines the string left to first (series of) = is assigned to the loop variable I (name) and everything after first (series of) = is assigned to the loop variable J (value) which can be also an empty string if there is nothing after first equal sign.

The two strings are output with command ECHO with a colon and a space between name and value and with enclosing the value in double quotes and with a comma at end of the line and this output is written with carriage return and line-feed into the opened file env-config.js.

Last after all lines of .env are processed by FOR /F and this file is closed, there is output with ECHO the string } with the newline characters carriage return and line-feed.

The execution of all commands of the command block is finished with echo } and so cmd closes the file env-config.js containing now all the output characters/bytes.

The current directory is not necessarily the directory containing the batch file. So if it is expected that the file .env is in the directory of the batch file and the file env-config.js should be created also in the directory of the batch file, it would be better to use the following single command line batch file.

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

The multi-line version is:

@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 (drive and path of batch file argument 0) expands to the full directory path of the batch file always ending with a backslash and therefore concatenated without one more \ with the file names .env and env-config.js.

The FOR /F option usebackq is used additionally to get the fully qualified file name of the file .env in the batch file directory enclosed in " interpreted as file name of a text file of which lines to process and not as string to process.

Please note that the directory separator on Windows is \ and not / as on Linux/Mac as described by Microsoft in the documentation about 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?

For completeness a multi-line batch file which outputs the lines in file .env in directory of the batch file into the file env-config.js in directory of the batch file without a comma on the last line read from .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

There can be removed both %~dp0 to use the current directory instead of the batch file directory.

The IF condition results in the output of a string with a comma, a carriage return and a line-feed if the currently processed line is not the first line with data to output read from the file .env.

The command SET with option /P is used here with no environment variable name to output the reformatted data to handle STDOUT without a carriage return and line-feed which does not wait for user input because of the device NUL is used as STDIN (standard input) which cannot be opened to read something.

The additional command line echo( results in the output of just carriage return and line-feed to terminate also the last line with data read from .env not having a comma at end.

These are special "hacks" not officially documented by Microsoft, but documented in many answers on Stack Overflow and in DosTips forum topics ECHO. FAILS to give text or blank line - Instead use ECHO/

For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.

  • call /? ... explains batch file argument referencing as done with %~dp0
  • echo /?
  • endlocal /?
  • for /?
  • if /?
  • set /?
  • setlocal /?

See also the Microsoft documentation about Using command redirection operators.

  • Related