2

I'm trying to transform the multi-line script

@echo off

for /f "eol=: delims=" %%D in ('dir /b /s /ad *^|sort /r') do (
  pushd "%%D"
  for %%F in (*) do move "%%F" "..\%%~nxD - %%F" >nul
  popd
  rd /s /q "%%D"
)
exit

to a single line in order to register it as a context menu entry. To my understanding, the equivalent single line command would be this one:

@echo off && for /f "eol=: delims=" %%D in ('dir /b /s /ad *^|sort /r') do (pushd "%%D" && for %%F in (*) do move "%%F" "..\%%~nxD - %%F" >nul && popd && rd /s /q "%%D") && exit

...but it produces different results than the original script.

Replacing && with & produces a third different result but as far as I know I would still need the && anyway in this case.

What could I be doing wrong?

8
  • This is merely a guess, but && means only run the command if the previous was successful. There might be moves that are failing in the inner loop? You can use just & to string the commands together.
    – ernie
    Commented Aug 4, 2017 at 22:30
  • Tried that already as well but it didn't work either. It does make sense to wait for the previous command to end, though. As far as I know, each line break acts as a && in that sense so it should be fundamentally the same. I'm proven wrong though, so I've had to ask. Commented Aug 4, 2017 at 22:39
  • 2
    alternatively, you might be able to save this to a batch file and have your context menu entry call the batch script.
    – ernie
    Commented Aug 4, 2017 at 22:45
  • 1
    That's the backup plan but I'd rather be able to avoid unnecessary files and absolute paths. Commented Aug 4, 2017 at 22:57
  • 1
    I wonder if the opening and closing parenthesis of the DO portion of the FOR loop aren't happy with being separated by &&. Also, the opening echo off and final exit statements are probably unnecessary. Commented Aug 5, 2017 at 2:26

1 Answer 1

2

It turns out that for … do is “greedy”.  (I don’t remember knowing this before, and it doesn’t seem to be prominently documented in for /?.)

Here’s what I mean:

Consider script cow1.bat:

@echo off
for %%a in (how now brown) do echo %%a
echo cow

Pretty trivial.  When run, it outputs

how
now
brown
cow

as we might guess.

Consider script cow2.bat:

@echo off
for %%a in (how now brown) do echo %%a & echo cow

The same, except lines 2 and 3 have been joined, delimited by a &.  Guess what!?  When run, it outputs

how
cow
now
cow
brown
cow

It’s being interpreted as if it were

@echo off
for %%a in (how now brown) do (echo %%a & echo cow)

(Changing & to && didn’t have any effect.)  In a (to me) bigger surprise,

@echo off
for %%a in (how now brown) do (echo %%a) & echo cow

also functioned the same as cow2.  I had to use

@echo off
(for %%a in (how now brown) do echo %%a) & echo cow

to get the original, cow1 behavior.


How does this affect the OP?

for %%F in (*) do move "%%F" "..\%%~nxD - %%F" >nul && popd && rd /s /q "%%D"

is analogous to cow2:  If %%D (the directory we just pushd’ed into) is beatles, then the above code segment does this:

move "george" "..\beatles - george" >nul    &&    popd    &&    rd /s /q "beatles"
move "john"   "..\beatles - john"   >nul    &&    popd    &&    rd /s /q "beatles"
move "paul"   "..\beatles - paul"   >nul    &&    popd    &&    rd /s /q "beatles"
move "ringo"  "..\beatles - ringo"  >nul    &&    popd    &&    rd /s /q "beatles"

(whitespace added for clarity).  So it’s moving the first file (george), and then popd’ing out of the beatles directory and deleting it, leaving the other files to be reaped by the roomba.

Recommendation (TL;DR)

Try

for /f "eol=: delims=" %%D in ('dir /b /s /ad *^|sort /r') do (pushd "%%D" && (for %%F in (*) do move "%%F" "..\%%~nxD - %%F" >nul) && popd && rd /s /q "%%D")
                                                                              ↑                                                   ↑
 

Trivial question:

Why do we need the * in the dir /b /s /ad * subcommand?

5
  • I won't use this as an excuse but I got this script by request as so far I've been unable to find where to learn batch from so I'm afraid I can't answer that question. I'd really like to learn or at the very least understand the code I'm given but it's rendered pointless without the base knowledge I'm missing. Commented Aug 5, 2017 at 10:00
  • "Why do we need the * in the dir /b /s /ad * subcommand?" You don't.
    – DavidPostill
    Commented Aug 5, 2017 at 12:45
  • @JesúsCruz: Well, try running dir /b /s /ad * and dir /b /s /ad at various times, in various places, situations and contexts.  If they always produce the same output, you may become confident that David and I are correct, and that they are equivalent. If you're bold, you can delete the superfluous two characters from your script. On the other hand, if you ever get different outputs from the two commands, please come back here and let us know, because then you will have taught us something. Commented Aug 5, 2017 at 18:33
  • I will remove it and see if anything stops working. One more question, even if a bit off-topic (I don't know if it's worth opening a new topic): the script doesn't work with folders with ' on their filenames. Any idea why? Commented Aug 6, 2017 at 22:26
  • Actually, now that I've removed the * the script works with single quote characters in filenames. Thank you for everything. Commented Aug 7, 2017 at 20:46

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .