Define a new handle (Similar to STDOUT) - batch-file

I was looking at redirecting handles in batch, when I noticed this:
Here is the link
It mentions that handles 3-9 are undefined and can be defined by a program. Now I've read about doing this in C#, but I wondered if this was possible in cmd/batch - and if it is, what are its limitations/use.
If it is not possible in cmd, how would I go about using this, and could it be a solution to outputting data to the screen and redirecting it to a file at the same time (a problem which has not been able to be done legitimately at the same time).
Thanks, Mona.

A Batch file is limited to manage just two files: STDIN for input (SET /P) operations, and STDOUT for output (ECHO, etc.) operations; however, we could have access to more than one input and output files in a Batch file. How to do that? In a relatively easy way: just connect the additional files to unused handles (3-9) and use the appropiate handle in the input (SET /P <&#) or output (ECHO >&#) commands.
The Batch file below merge the lines of 3 input files into one output file with larger lines:
#echo off
setlocal EnableDelayedExpansion
3<input2.txt 4<input3.txt (
for /F "delims=" %%a in (input1.txt) do (
set line=
rem Read from input2.txt (and write line from input1 to output.txt):
set /P line=%%a <&3
rem Read from input3.txt (and write line from input2 to output.txt):
set /P line=!line! <&4
rem Write line from input3 to output.txt:
echo(!line!
)
) >output.txt
The same method may be used to generate several output files.
See: Access to SEVERAL files via Standard Handles
And a more technical explanation here

Related

Saving variables in a text file

I'm trying to load and save variables into a file.
For example:
#echo off
set /a number=1
echo %number%>text.txt
How do I store the number from the text file in a variable for example variable1?
As mentioned by aschipfl, there are two ways to do it:
Using set /P (redirect variable to a text file and read (the file) with set /p).
Parse the file using a for /F loop.
As the first way is already mentioned by Tiw, I will only deal with the second one. You should do:
#echo off
set "number=1"
(echo %number%)>text.txt
for /F "delims= eol=" %%A IN (text.txt) do set "variable1=%%A"
Note that:
/a option in set is used only to perform arithmetic operations. It doesn't mean that the interpreter will 'see' this as a number.
The parentheses are added for security. They prevent echoing an extra space in the txt file. echo %number%>txt won't work if %number% is <10 because 0 is STDIN, 1 is STDOUT, 2 is STDERR and numbers from 3 to 9 are undefined. Apparently, it's sending STDIN/STDOUT/STDERR/UNDEFINED of nothing to a file.
Further reading:
https://ss64.com/nt/syntax-redirection.html
https://ss64.com/nt/for_f.html
https://ss64.com/nt/set.html
Your code will lead to problem, better change to:
#echo off
set /a number=1
>text.txt echo %number%
Another way of the last line is:
echo %number% 1>text.txt
1 means STDOUT, that's why your code won't write 1 to the file text.txt.
And to read the file into variable1:
set /p variable1=<text.txt
Note when there're multiple lines in the file, only first line will be read into the variable1.
From comment, and changed a little bit:
if not exist "%~dp0settings.gsf" (
echo.>"%~dp0settings.gsf"
goto :createsave
) else (
echo Reading your savefile...
set /p lang=<settings.gsf
)
%~dp0 will end with \, so no need to add another \.
You should close the else block, and it's better to indent and put ) else ( in a single line.
Try change according this, see if it works.
-- Indentation is good for readability and debugging, in other programming languages too.

comparing filenames with .bat and writing missing entries into .txt

i want to compare some files in a folder and then write missing ones into a text file.
i've got a folder
c:\sessions\
with files in it. those files are looking like:
Blabla-1111-FOO
Blabla-1111-BAR1
Bleble-2222-FOO
Bleble-2222-BAR1
Bleble-2222-BAR2
Bleble-2222-BAR3
Bleble-2222-BAR4
Blublu-5678-FOO
Blublu-5678-BAR1
Blublu-5678-BAR2
Blublu-5678-BAR3
Blublu-5678-BAR4
Blublu-5678-BAR5
Bleble-6666-FOO
Bloblo-7777-FOO
Bloblo-8888-FOO
Bloblo-9999-FOO
i need a .bat file which writes down all files ending with -FOO where no matching -BAR exists into a noBARs.txt. So in this case the noBARs.txt should look like this:
Bleble-6666-FOO
Bloblo-7777-FOO
Bloblo-8888-FOO
Bloblo-9999-FOO
There can be multiple -BAR files that belong to one -FOO file.
Thanks in advance guys.
You could try the following:
#echo off
setlocal enabledelayedexpansion
set "source=c:\sessions"
set "output=noBARs.txt"
set "find=*foo"
set "repl=bar*"
1>"%output%" (
for %%e in ("%source%\%find%") do (
set "file=%%e"
if not exist "!file:~0,-3!%repl%" (
echo %%~ne
)
)
)
This will iterate through all the files that contain *foo within their filename. Then it replaces the last 3 characters of the filename with bar* and checks for the existence of the file. If the corresponding bar file doesn't exist it will write the original filename to the output file noBARs.txt.
I've added an alternative version of the script based on the last comment of dEEkAy in which he states that files ending with FOO followed by a numerical constant should be tested as well. I'm still quite uncertain what the exact intention of dEEkAy is but I'm assuming that the file names *FOO* should be handled the same as *FOO.
The following input:
Blabla-1111-FOO
Blabla-1111-BAR1
Bleble-2222-FOO
Bleble-2222-BAR1
Bleble-2222-BAR5
Blublu-3333-FOO6
Blublu-3333-BAR1
Blublu-5678-FOO
Blublu-5678-FOO1
Blublu-5678-BAR2
Blublu-5678-BAR4
Bleble-6666-FOO
Bloblo-7777-FOO5
Bloblo-7777-BAR8
Bloblo-9999-FOO
Blublu-9999-FOO9
Bloblo-!^^!-FOO
Will produce the following output:
Bleble-6666-FOO
Bloblo-!^^!-FOO
Bloblo-9999-FOO
Blublu-9999-FOO9
The alternative version of the script is also more robust than the original. It should now be capable of handling file names and paths that contain exclamation marks and carets appropriately. Unfortunately, it does come with a cost. Due to the call command which will be executed for each iteration of the for-loop, the script has become a lot slower. Perhaps someone with more batch-file scripting experience can come up with a better solution.
#echo off
setlocal disabledelayedexpansion
set "source=c:\sessions"
set "output=noBARs.txt"
set "find=foo"
set "repl=bar"
1>"%output%" (
for %%e in ("%source%\*%find%*") do (
set "path=%%~dpe"
set "file=%%~ne"
setlocal enabledelayedexpansion
call :ReplaceEndOfString file match "%find%" "%repl%"
if not exist "!path!!match!*" (
echo !file!
)
endlocal
)
)
exit /b
:ReplaceEndOfString (__in *source, __out *dest, __in find, __in repl) {
set "temp=!%1:*%~3=%~3!"
set "%2=!%1:%temp%=%~4!"
exit /b
}
Keep in mind that "%source%\*%find%*" will match any file that contains %find% within its file name, whereas "%source%\*%find%?" only matches file names that end with %find% including one optional character that could be anything.
I just happened to need some code similar to this and decided to take another look. The main bottleneck of the code seems to be the invocation of the call command. While I don't see a way around this (other than using macros), the execution time of the call command can be greatly improved (with a factor of 2, roughly).
Calling a function by its label seems to be a lot slower than calling the set command. I'm not entirely sure but I can remember something about calling labels being slow due to requiring a rescan of the entire script file. Carets and exclamation marks within file names also appear to remain intact because the delayed expansion phase seems to occur before the set command is called.
#echo off
setlocal DisableDelayedExpansion
set "source=c:\sessions"
set "output=nobars.txt"
set "find=foo"
set "repl=bar"
1>"%output%" (
for %%e in ("%source%\*%find%*") do (
set "path=%%~dpe"
set "file=%%~ne"
setlocal EnableDelayedExpansion
set "end=!file:*%find%=%find%!"
call set "match=%%file:!end!=%repl%%%"
if not exist "!path!!match!*" (
echo !file!
)
endlocal
)
)
exit /b

How to get just the first line of a text file written into a new text file using a batch file?

Okay I have several lines in a text file. I want to get the first line and save it in another file. For example this is the text file:
put returns between paragraphs
for linebreak add 2 spaces at end
for linebreak add 2 spaces at end2
for linebreak add 2 spaces at end3
I want put returns between paragraphs to be saved into another file.
I used
for /f "tokens=" %%A in ('findstr /r "^[0-9][0-9]*$" <"C:\Users\Sherlock\Desktop\AbcImport\123.txt"') do echo 123>>1234.txt
pause
But it doesn't work at all.
How to get just the first line of a text file written into a new text file using a batch file?
Option 1 - SET /P : This is the simplest and fastest pure batch solution, provided the line does not exceed 1021 bytes, and it does not end with control characters that must be preserved. The size of the file does not matter - it will always read and write the first line very quickly.
#echo off
setlocal enableDelayedExpansion
set "ln="
<"input.txt" set /p "ln="
>"output.txt" (echo(!ln!)
Option 2 - FOR /F : This will work with lines up to ~8191 bytes long, but it can be slow if the file is really large because the FOR /F loop must read the entire file before it processes the first line. This solution is basically the same as the Mofi answer, except it disables the EOL option, so it never ignores the first line, regardless what the first character is. It does have a limitation that it will skip empty lines, so technically it does not give the correct result if the first line is empty:
#echo off
for /f usebackq^ delims^=^ eol^= %%A in ("input.txt") do echo(%%A>"output.txt"&goto :break
:break
There is a way to preserve the first line if it is empty using pure batch, but I would not bother. I would move on to ...
Option 3 - JREPL.BAT, or some other non-batch solution : Batch is quite poor at manipulating text files. You are much better off using some other scripting language like VBScript, JScript, or Powershell. Or a Windows port of any number of unix utilities.
I would use JREPL.BAT - a hybrid JScrpit/batch regular expression text processing utility that runs natively on any Windows machine from XP onward. It is way overkill for such a simple task, but it is an extremely handy, powerful, and efficient tool to have in your arsenal. Once you have it, then it can be used for many text processing tasks. Full documentation is embedded within the script.
jrepl "^.*" "$&" /jendln "quit=true" /f "input.txt" /o "output.txt"
Use CALL JREPL if you put the command within a batch script.
Here is the batch code to write just first non blank/empty line of a text file into another text file.
#echo off
for /F "usebackq delims=" %%I in ("InputTextFile.txt") do (
echo %%I>"OutputTextFile.txt"
goto ContinueAfterLoop
)
:ContinueAfterLoop
InputTextFile.txt is the file in current directory containing the first line to copy.
OutputTextFile.txt is the file created in current directory with first line from input file copied into this output file.
The command GOTO is used to exit the loop after first line is processed and continue the batch file below the loop.
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.
echo /?
for /?
goto /?
Read also the Microsoft article about Using Command Redirection Operators.
You can use use this command:
SetLocal EnableDelayedExpansion
for /f "tokens=* delims=;" %%m in ("C:\Users\Sherlock\Desktop\AbcImport\123.txt") do (
set /p FirstLine=<%%m
echo !FirstLine!>>1234.txt
)
and for multiple file:
SetLocal EnableDelayedExpansion
for %%a in ("*") do (
for /f "tokens=* delims=;" %%m in ("%%a") do (
set /p FirstLine=<%%m
echo !FirstLine!>>1234.txt
)
)
rem Get the first line of a text file:
set /P "line=" < "C:\Users\Sherlock\Desktop\AbcImport\123.txt"
rem Write it into a new text file:
echo %line%> 1234.txt

Redirecting command input using <

Input redirection is working for .exe files or internal windows commands.
app.exe < ListOfNames.txt
sort < input.txt
However it isn't working when I try to redirect it into a batch script.
test.bat :-
#echo off
echo %1 %2
Running it using :-
test.bat<input.txt
where input.txt has two strings.
However, it is working fine for redirecting output even in case of batch scripts.
Is this the expected behavior or I am making some syntax mistake? Is there any other way to read arguments from a file instead of manually parsing it?
Parameters that are provided on the command line are completely different than stdin ( where your redirected input goes). This is true for both batch scripts as well as .exe programs.
Some programs are designed to accept the same values via command line arguments or stdin. But that is not the norm. That is a feature that is provided by the developer of the program.
If you want to read redirected input within a batch script, then you must do one of the following.
To read a single line:
set /p "ln="
echo %ln%
To read all lines in a loop:
for /f "delims=" %%A in ('findstr "^"') do (
echo %%A
)
Additionally to dbenhams answer, you could also read multiple lines with set/p for a input redirection, like myBatch.bat < commands.txt
#echo off
set "line_1="
set "line_2="
set "line_3="
set /p line_1=
set /p line_2=
set /p line_3=
set line_
But this would fail with an input pipe like type commands.txt | myBatch.bat

Windows Batch help in setting a variable from command output [duplicate]

This question already has answers here:
Set output of a command as a variable (with pipes) [duplicate]
(6 answers)
Closed 7 years ago.
I need to run a simple find command and redirect the output to a variable in a Windows Batch File.
I have tried this:
set file=ls|find ".txt"
echo %file%
But it does not work.
If I run this command it works without problems:
set file=test.txt
echo %file%
So obviously my command output is not being set to my variable. Can anyone help? Thanks
I just find out how to use commands with pipes in it, here's my command (that extracts the head revision of an svn repo) :
SET SVN_INFO_CMD=svn info http://mySvnRepo/MyProjects
FOR /f "tokens=1 delims=" %%i IN ('%SVN_INFO_CMD% ^| find "Revision"') DO echo %%i
First of all, what you seem to expect from your question isn't even possible in UNIX shells. How should the shell know that ls|find foo is a command and test.txt is not? What to execute here? That's why UNIX shells have the backtick for such things. Anyway, I digress.
You can't set environment variables to multi-line strings from the shell. So we now have a problem because the output of ls wouldn't quite fit.
What you really want here, though, is a list of all text files, right? Depending on what you need it's very easy to do. The main part in all of these examples is the for loop, iterating over a set of files.
If you just need to do an action for every text file:
for %%i in (*.txt) do echo Doing something with "%%i"
This even works for file names with spaces and it won't erroneously catch files that just have a .txt in the middle of their name, such as foo.txt.bar. Just to point out that your approach isn't as pretty as you'd like it to be.
Anyway, if you want a list of files you can use a little trick to create arrays, or something like that:
setlocal enabledelayedexpansion
set N=0
for %%i in (*.txt) do (
set Files[!N!]=%%i
set /a N+=1
)
After this you will have a number of environment variables, named Files[0], Files[1], etc. each one containing a single file name. You can loop over that with
for /l %%x in (1,1,%N%) do echo.!Files[%%x]!
(Note that we output a superfluous new line here, we could remove that but takes one more line of code :-))
Then you can build a really long line of file names, if you wish. You might recognize the pattern:
setlocal enabledelayedexpansion
set Files=
for %%i in (*.txt) do set Files=!Files! "%%i"
Now we have a really long line with file names. Use it for whatever you wish. This is sometimes handy for passing a bunch of files to another program.
Keep in mind though, that the maximum line length for batch files is around 8190 characters. So that puts a limit on the number of things you can have in a single line. And yes, enumerating a whole bunch of files in a single line might overflow here.
Back to the original point, that batch files have no way of capturing a command output. Others have noted it before. You can use for /f for this purpose:
for /f %%i in ('dir /b') do ...
This will iterate over the lines returned by the command, tokenizing them along the way. Not quite as handy maybe as backticks but close enough and sufficient for most puposes.
By default the tokens are broken up at whitespace, so if you got a file name "Foo bar" then suddenly you would have only "Foo" in %%i and "bar" in %%j. It can be confusing and such things are the main reason why you don't ever want to use for /f just to get a file listing.
You can also use backticks instead of apostrophes if that clashes with some program arguments:
for /f "usebackq" %%i in (`echo I can write 'apostrophes'`) do ...
Note that this also tokenizes. There are some more options you can give. They are detailed in the help for command.
set command has /p option that tells it to read a value from standard input. Unfortunately, it does not support piping into it, but it supports reading a value from a first line of existing file.
So, to set your variable to the name of a first *.txt file, you could do the following:
dir /b *.txt > filename.tmp
set /p file=< filename.tmp
del /q filename.tmp
It is important not to add a space before or even after =.
P. S. No fors, no tokens.
Here's a batch file which will return the last item output by find:
#echo off
ls | find ".txt" > %temp%\temp.txt
for /f %%i in (%temp%\temp.txt) do set file=%%i
del %temp%\temp.txt
echo %file%
for has a syntax for parsing command output, for /f "usebackq", but it cannot handle pipes in the command, so I've redirected output to a temporary location.
I strongly recommend, given that you have access to ls, that you consider using a better batch language, such as bash or even an scripting language like python or ruby. Even bash would be a 20x improvement over cmd scripting.
The short answer is: Don't!
A windows shell env var can hold a max of 32 Kb and it isn't safe to save output from programs in them.
That's why you can't. In batch script you must adopt another programming style. If you need all of the output
from the program then save it to file. If you only need to check for certain properties then pipe the output into
a program that does the checking and use the errorlevel mechanism:
#echo off
type somefile.txt | find "somestring" >nul
if %errorlevel% EQU 1 echo Sorry, not found!
REM Alternatively:
if errorlevel 1 echo Sorry, not found!
However, it's more elegant to use the logical operators Perl style:
#echo off
(type somefile.txt | find "somestring" >nul) || echo Sorry, not found!
It's not available in DOS, but in the Windows console, there is the for command. Just type 'help for' at a command prompt to see all of the options. To set a single variable you can use this:
for /f %%i in ('find .txt') do set file=%%i
Note this will only work for the first line returned from 'find .txt' because windows only expands variable once by default. You'll have to enable delayed expansion as shown here.
what you are essentially doing is listing out .txt files. With that, you can use a for loop to over dir cmd
eg
for /f "tokens=*" %%i in ('dir /b *.txt') do set file=%%i
or if you prefer using your ls, there's no need to pipe to find.
for /f "tokens=*" %%i in ('ls *.txt') do set file=%%i
Example of setting a variable from command output:
FOR /F "usebackq" %%Z IN ( `C:\cygwin\bin\cygpath "C:\scripts\sample.sh"` ) DO SET BASH_SCRIPT=%%Z
c:\cygwin\bin\bash -c '. ~/.bashrc ; %BASH_SCRIPT%'
Also, note that if you want to test out the FOR command in a DOS shell, then you need only use %Z instead of %%Z, otherwise it will complain with the following error:
%%Z was unexpected at this time.

Resources