22

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

Joey
  • 344,408
  • 85
  • 689
  • 683
Steve
  • 295
  • 2
  • 3
  • 8

8 Answers8

9

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
Grokwik
  • 511
  • 4
  • 6
6

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.

Joey
  • 344,408
  • 85
  • 689
  • 683
  • Thanks for the detailed explanation Johannes. In my example I will not have multiple instances of this file type. (.txt) There will only be one file. As I mentioned in my comment above I have a script that appears to work: for %%i in (*.txt) do set file=%%i (In the console results it had the only txt filename listed) But I have tried echo'ing the variable on the line after and it did not work. I also tried copying the file using the variable and it did not work. How can I use this variable after it is set? Here is my copy script: for %%i in (*.txt) do set file=%%i copy %%i c:\temp\. – Steve Nov 17 '09 at 13:28
  • 1
    "First of all, what you seem to expect from your question isn't even possible in UNIX shells." This is not true. Granted, the OP's syntax is wrong, but it is doable in Unix: TheVariable=``ls|grep .txt`` works exactly as OP desired. I found this question because of my need for that exact capability in DOS. I guess this is one more example of how brain-dead the DOS shell is – DCookie Dec 09 '09 at 19:15
  • Iff the OP would expect it to work the same as in UNIX shells I would expect backticks to be there. That's why I didn't consider it a mistake of that sort. And as for brain-dead ... why the heck do you even *need* this in DOS? I haven't seen a DOS user in years. Aside from that, nearly every line I used in my answer wouldn't run in DOS anyway, so it's essentially a moot point. – Joey Dec 09 '09 at 19:58
  • 1
    I guess my point is that it was pretty clear to me from the title of this question the OP wanted to set a variable to the output of a command, which you apparently really cannot do directly with DOS. As for why? Sometimes we're given an environment to accomplish things in and a task to accomplish. I use this all the time in building a development Oracle database based on a production database copy. A DOS script gets things done - it HAS to, because the *nix option is not there. – DCookie Dec 09 '09 at 21:26
  • Okay, I see how you can do it now. Unintuitive and highly awkward, but possible, as this code to set a variable to the current time shows: `for /f "usebackq delims==" %i in (`time /t`) do @set thetime=%i` (there's backticks around the time /t but I can't figure out how to make them show up in this comment block :-| – DCookie Dec 09 '09 at 21:49
  • Escape them with `\\`. Still, cmd is *not* DOS. – Joey Dec 09 '09 at 22:35
  • The code the OP posted was what he tried to do in *command.exe*. The fact that Bash supports what he wanted to do by using a subshell (backticks or *$()*) around the command shows the ridiculousness of the situation. – bias Nov 26 '13 at 16:21
  • @bias: There is no shell whose executable is `command.exe`. – Joey Nov 26 '13 at 16:23
  • Right, command.com is the DOS and Windows 9x shell and cmd.exe (Command Prompt) is the NT default shell. – bias Nov 26 '13 at 20:14
  • Which, raises the issue being mentioned earlier. On Windows 9x command.com *is* a DOS program. – bias Nov 26 '13 at 20:15
3

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.

Olexa
  • 577
  • 2
  • 16
1

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.

Barry Kelly
  • 41,404
  • 5
  • 117
  • 189
1

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!
Henrik
  • 332
  • 3
  • 12
0

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.

Community
  • 1
  • 1
shf301
  • 31,086
  • 2
  • 52
  • 86
  • 1
    That will set `file` to the **last** line of text because it set's `file` to EVERY line in whatever it's doing one at a time. This means that `file` DOES get set to the first line, but then is immediately overwritten by the second line. This continues until the last line, which remains the value set in `file` because it's not overwritten. Also `find .txt` will give you an error, because `find` requires a sample of text to search for, and a stream of text to search, like `find "hello" *.txt`, or `echo %data% | find "hello"`. `find .txt` will error because **FIND** has no string to search. – James K Nov 03 '12 at 20:08
0

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
ghostdog74
  • 327,991
  • 56
  • 259
  • 343
  • Please, don't mis-use `for /f` for such things. You can do `for %%i in (*.txt)` perfectly fine and won't even have problems with spaces in file names as your code currently would have. – Joey Nov 17 '09 at 07:04
  • so what? doesn't mean it cannot be used. – ghostdog74 Nov 17 '09 at 07:41
  • It's unnecessary and creates problems in this case that can be avoided. – Joey Nov 17 '09 at 08:12
  • a lot of things are unnecessary in batch, not just this. – ghostdog74 Nov 17 '09 at 09:03
  • Thanks for the feedback a few followup questions. This appears to work as my script: for %%i in (*.txt) do set file=%%i (In the console results it had the only txt filename listed) But I have tried echo'ing the variable on the line after and it did not work. I also tried copying the file using the variable and it did not work. How can I use this variable after it is set? Here is my copy script: for %%i in (*.txt) do set file=%%i copy %%i c:\temp\ – Steve Nov 17 '09 at 13:24
0

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.
desm
  • 311
  • 2
  • 6