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

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.

Related

How to sort the argument list in a a Windows 10 batch file

I need to call a program with a list of file names, but I need to find and extract the first file on the list taken as sorted order and pass the rest to the program.
Specifically, I want to pass a list of files selected with the QTTabbar application launcher and execute exiftool so that the first file on the list is used for the "-TagsFromFile" option, and then process all of the rest of the files so that they get the "-AllDates" option applied. So my first attempt was:
exiftool -TagsFromFile %1 -AllDates %*
This would put the first file on the list, but since exiftool would be setting it to the same value as it already has, that would be acceptable.
However, I discovered that QTTabbar did not pass the arguments to the batch file in the lexigraphically sorted order by name as I was expecting. So I figured I needed to sort the list.
I found the way to sort the arguments in How to sort the arguments dropped to a batch file? but in that solution there is a loop and a program is invoked once for each argument, rather than building a new argument list.
for /f "delims=" %%a in ('cmd /c ^"for %%i in ^(%*^) do #echo %%~i^"^|sort') do (
echo use "%%a"
)
Instead of "echo use "%%a", I need to build a new argument list that I can pass to exiftool. Ideally I could build a list and then replace the original argument list with the new one, like the "set" command in Bash. Failing that, I could build a new list and use that, but I don't know how to build a list and I don't know how to reference the first element if I had one.
How do I do this?
EDIT:
The files are selected in the File Explorer GUI. The order they are presented to the batch file is determined by Windows. Here is the output of "echo %*" from the batch file:
"C:\Users\user1\Desktop\setAB.test\00000920.jpg" "C:\Users\user1\Desktop\setAB.test\00000913.jpg" "C:\Users\user1\Desktop\setAB.test\00000914.jpg" "C:\Users\user1\Desktop\setAB.test\00000915.jpg" "C:\Users\user1\Desktop\setAB.test\00000916.jpg" "C:\Users\user1\Desktop\setAB.test\00000917.jpg" "C:\Users\user1\Desktop\setAB.test\00000918.jpg" "C:\Users\user1\Desktop\setAB.test\00000919.jpg"
As you can see, the last file appears first. I don't know why. Sometimes they are in reverse order.
So, the batch file gets invoked as:
ex.bat "C:\Users\user1\Desktop\setAB.test\00000920.jpg" "C:\Users\user1\Desktop\setAB.test\00000913.jpg" "C:\Users\user1\Desktop\setAB.test\00000914.jpg" "C:\Users\user1\Desktop\setAB.test\00000915.jpg" "C:\Users\user1\Desktop\setAB.test\00000916.jpg" "C:\Users\user1\Desktop\setAB.test\00000917.jpg" "C:\Users\user1\Desktop\setAB.test\00000918.jpg" "C:\Users\user1\Desktop\setAB.test\00000919.jpg"
And I want exiftool to run as:
exiftool -TagsFromFile "C:\Users\user1\Desktop\setAB.test\00000913.jpg" -AllDates "C:\Users\user1\Desktop\setAB.test\00000913.jpg" "C:\Users\user1\Desktop\setAB.test\00000914.jpg" "C:\Users\user1\Desktop\setAB.test\00000915.jpg" "C:\Users\user1\Desktop\setAB.test\00000916.jpg" "C:\Users\user1\Desktop\setAB.test\00000917.jpg" "C:\Users\user1\Desktop\setAB.test\00000918.jpg" "C:\Users\user1\Desktop\setAB.test\00000919.jpg" "C:\Users\user1\Desktop\setAB.test\00000920.jpg"
So your code was really close. All you needed to add were a few basic concepts to get the first argument into a variable which can be done by checking if a variable is defined or not. Then use the SET command to rebuild all the arguments back into another variable. This requires delayed expansion as described in this question.
#ECHO OFF
setlocal enabledelayedexpansion
set "first="
set "all="
for /f "delims=" %%a in ('cmd /c ^"for %%i in ^(%*^) do #echo %%~i^"^|sort') do (
if not defined first set "first=%%~a"
set "all=!all!"%%~a" "
)
exiftool -TagsFromFile "%first%" -AllDates %all%
endlocal
Very similar to the method already provided by Squashman, but suitable for filepaths containing ! characters.
#For %%G In (First Rest) Do #Set "%%G="
#For /F "Delims=" %%H In (
'"(For %%G In (%*) Do #Echo "%%~G") | %SystemRoot%\System32\sort.exe"'
) Do #If Not Defined First (Set "First=%%H") Else (SetLocal EnabledelayedExpansion
For /F "Delims=" %%I In ("!Rest!%%H") Do #EndLocal & Set "Rest=%%I")
exiftool.exe -TagsFromFile %First% -AllDates %Rest%

Batch loopvariable manipulation

I have a small problem with a .bat file that I have to build to manipulate a specific .csv.
I want the .bat to read the line of the file, and then check for the first three letters of that line. At the end there should be n-files where file xxx.csv contains the lines of the input.csv with xxx as the first three letters of line.
First things first, I don't even know if it is possible to do it this job in a batch-file, because the file has >85000 lines and may even get much bigger. So if it is impossible you can directly tell me that.
for /f "delims=" %%a in (input.CSV) DO (
echo %%~a:~0,3
pause
)
I want to "output" the first three letters of %%a.
It would be great if you could help me.
Phil
Substring substitution only works with environment variables (%var%), but not with metavariables (%%a) (as Mofi already commented). And because you are setting and using a variable within the same command block, you need delayed expansion:
setlocal enabledelayedexpansion
for /f "delims=" %%a in (input.CSV) DO (
set "var=%%~a"
echo !var:~0,3!
pause
)
(there are methods without delayed expansion, but they make use of call, which slows things down)

How to get the FIND command output count value into a variable in batch scripting?

I am using a command:
find /c "abc" "C:\Users\abc\Desktop\project\string.txt"
Output:
---------- C:\Users\abc\Desktop\project\string.txt: 4
I want to assign this value 4 to a variable so that I can use it for an if statement.
I would use:
For /F %%A In ('Find /C "abc"^<"C:\Users\abc\Desktop\project\string.txt"') Do (
Set "mlc=%%A")
Your %mlc% varaiable would hold the matched line count.
I'm not sure if this is the best method to do this, but it works:
find /c "abc" "C:\Users\abc\Desktop\project\string.txt" > tmpFile
set /p myvar= < tmpFile
del tmpFile
with your snytax the output is ---------- C:\Users\abc\Desktop\project\string.txt: 4
There is another syntax: type file.txt|find /c "abc", which gives you a beautiful output of just:
15
To get it into a variable, use a for /f loop:
for /f %%a in ('type file.txt^|find /c "abc"') do set count=%%a
echo %count%
(for use directly on commandline (not in a batchfile) use %a instead of %%a)
I am not a batch script / Windows command line pro, but I got this to work with the following, without a temporary file:
for /f "delims=" %%a in ('dir "%~dpSomeFolder\*.suffix" /b ^|find /c "suffix"') do set "fileCount=%%a"
Explanation:
I found it very confusing, why assigning a variable with a batch script, is so weird, considering it's "Windows", the most used operating system. Anyways this answer here is helpful. Even if it is a duplicate, I like the formatting more:
Assign command output to variable in batch file
%~dp0: basically translates to "path of this script". You can find info about this online.
SomeFolder\*.suffix: In my case this I was looking to count the number of files ending with a certain suffix. I had problems using the dir command with \s as this listed all the matches in subfolders I did not expect him too look. As if this was referenced to the path from which I executed this script from. Therefore, the path name with the asterisk "\*.suffix" solved that issue for me.
^|: When using the pipe sign "|" in "for command", specified inside the single quotation marks, you need to use a circumflex "^|", instead of just the "|", which you would normally use when just typing in the command in cmd (f.e. like dir "%~dp0Folder*.suffix" /b | find /c "suffix"
%%a: You have to use the "double percentage", as this is just a locale variable when writing this in a batch script.
FYI: you can have a look at the command help/ manual ("man" as I am used to Linux), with the command /? (f.e. dir /? or find /?)
I thought I would also mention how I then used this variable, as this might save some time for you ;) (batch code coloring somehow did not work here...).
IF %fileCount% NEQ 1 (ECHO Number of SUFFIX files does not equal 1! Found %fileCount% SUFFIX files inside the SomeFolder. Aborting script! & PAUSE & EXIT)

replace characters in TXT file using batch?

I don’t know if this is possible but I would like to make a batch, which replaces all, the backslashes contained in a txt file (C:\locations) with forward slashes.
THX..
EDIT:
Im trying to loop:
set str1=!Var1!
echo.%str1%
set str1=%str1:\=/%
to work together with:
set file=C:\text1.txt
FOR /F %%i IN (%file%) DO (
set username=%%i
echo (load "%%i") >> C:\text.txt
)
what I have so far is:
ECHO Retreving list of files...
dir /s /b c:\ICT\AUTOCAD_2010\*.LSP > C:\BART1.txt
Echo Looping variables...
set file=C:\text1.txt
FOR /F %%1 IN (%file%) DO (
set fred=%%1
echo %%1 > C:\tempme.txt
)
set fred=C:\tempme.txt
set fred=%fred:\=/%
echo (load "%fred%") >> C:\text2.txt
however this returns:
(load "C:/tempme.txt")
which is incorect.
Windows' PowerSheel is too limited for that. I suggest you using a scripting language like Perl. Here's a quick script that does that:
my #file = <STDIN>;
my $text = join('', #file);
$text =~ s/\\/\//g;
print($text);
You can launch it like
perl foo.pl < example.txt > result.txt
Using sed as suggested by Tichodroma is another very good option.
I'm a little confused as to what you really want. Your sample code has a FOR ... IN that only has one file in the IN (), i.e. the content of %fred%. Then you write that one filename into a temporary file. Afterwards you have SET that replaces the backslash with a forward slash in the name of the temporary file. But this never touches any file.
However, in your opening segment you want to replace backslashes with forward slashes in a file. So I'm focusing on that part. That would be done this way:
#ECHO OFF
SETLOCAL EnableDelayedExpansion
REM Here's a backslash: \
FOR /F "delims=" %%a IN ('TYPE %0') DO (
SET line=%%a
ECHO !line:\=/!
)
Please note that for simplicity this little batch file parses itself (%0) and, therefore, only replaces that one backslash. However, feel free to put whichever file next to TYPE.
EnableDelayedExpansion makes the syntax with the ! work (see SET /? for more info about delayed expansion). TYPE obviously writes a file to the console. The FOR /F "delims=" now grabs the output of that line per line (since delims= defines no delimiter for the tokenizer of FOR). The line-variable is necessary, because I don't think that there is a proper way to make the ECHO-line work with %%a.
The only downside is that this will remove all empty lines from the original file. I don't know if that's a problem for you.

This Supybot for windows batch install script needs to create another batch file

This Supybot for windows batch install script needs to create another batch file...
The Problem:
(1) I have a directory that has a file that ends with .conf
(2) There is only one file in this directory that ends with .conf
(3) But I don't know what this file starts with.. all I know is ????????.conf
(4) How do I set the filename.conf and remove the .conf part of the file name?
(5) As it is just the beginning of the filename that I need.
Example:
C:\runbot>find "supybot.ident: " | type *.conf > temp.txt
robotbot.conf
Outputs : robotbot.conf
The quest, is how do I set a variable=robotbot
=========================================================================
The Input was this file named "RootCode.conf" among many others
within the directory searched:
RootCode.conf
The Solution is:
FOR /F "tokens=1,2 delims=." %%a in ('FINDSTR /M "supybot.ident:" *.conf') DO SET USER=%%a&set dontneed=%%b
echo %USER%
pause
The Output is:
C:\runbot>FOR /F "tokens=1,2 delims=." %a in ('FINDSTR /M "supybot.ident:" *.con
f') DO SET USER=%a & set dontneed=%b
C:\runbot>SET USER=RootCode & set dontneed=conf
C:\runbot>echo RootCode
RootCode
C:\runbot>pause
Press any key to continue . . .
Winner... Special thanks Everyone
Your example of piping the output to typecommand is either wrong or useless. So I am assuming you mistyped and the real line was piping the other way around, and thus I am assuming that you are trying to find the filename of the file that contains the string "supybot.ident: ". In that case I would suggest to use findstr command instead.
FOR /F "tokens=*" %%a in ('FINDSTR /M "supybot.ident:" *.conf') DO SET USER=%%a
See HELP FINDSTR, HELP SET and HELP FOR for more information.
It's a bit unclear (to me, at least) what exactly you ask here. But if you need the output of a command, then use for /f:
for /f "delims=" %%x in ('some command ^| line') do set somevar=%%x
Note that you need to escape shell metacharacters in the command line (as they need to survive one parsing pass). Also note that you cannot set a variable to contain more than one line of text.

Resources