views:

1105

answers:

4

I need to run a simple find command and redirect the output to a variable in a Windows Batch File/DOS.

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

A: 

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.

shf301
A: 

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
A: 

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
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
so what? doesn't mean it cannot be used.
ghostdog74
It's unnecessary and creates problems in this case that can be avoided.
Joey
a lot of things are unnecessary in batch, not just this.
ghostdog74
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=%%icopy %%i c:\temp\
Steve
+1  A: 

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
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
"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
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
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
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
Escape them with `\\`. Still, cmd is *not* DOS.
Joey