So I'm having some trouble getting this batch file to work correctly. I've tried several different ways to find and replace a string.
Problem: We have to update a database ever so often and in doing so, a file gets changed. I have to change the one file back to get things to work. This line:
<network ipAdress="172.24.55.32" networkPort="9100" />
gets changed to this line:
<file filename="fffff" />
and needs to be changed back to the first line with the ip address.
I have tried to use a find and replace subroutine and can get it to work on the first part of changing "file filename" to "network ipAdress" but the quotes in the second section keep the script from working correctly. This is the code as I have it.
#echo off
setlocal
call :FindReplace "file filename" "network ipAdress" BCPrint.XML
Timeout 2
call :FindReplace "fffff" "172.24.55.32" networkPort="9100" BCPrint.XML
exit /b
:FindReplace <findstr> <replstr> <file>
set tmp="%temp%\tmp.txt"
If not exist %temp%\_.vbs call :MakeReplace
for /f "tokens=*" %%a in ('dir "%3" /s /b /a-d /on') do (
for /f "usebackq" %%b in (`Findstr /mic:"%~1" "%%a"`) do (
echo(&Echo Replacing "%~1" with "%~2" in file %%~nxa
<%%a cscript //nologo %temp%\_.vbs "%~1" "%~2">%tmp%
if exist %tmp% move /Y %tmp% "%%~dpnxa">nul
)
)
del %temp%\_.vbs
exit /b
:MakeReplace
>%temp%\_.vbs echo with Wscript
>>%temp%\_.vbs echo set args=.arguments
>>%temp%\_.vbs echo .StdOut.Write _
>>%temp%\_.vbs echo Replace(.StdIn.ReadAll,args(0),args(1),1,-1,1)
>>%temp%\_.vbs echo end with
I have also tried setting the second part as a variable like:
set R2=172.24.55.32" networkPort="9100
call :FindReplace "file filename" "network ipAdress" BCPrint.XML
Timeout 2
call :FindReplace "fffff" "%R2%" BCPrint.XML
With escape characters as well:
set ^R2=172.24.55.32" networkPort="9100^
I am sure there are a couple of different ways that I have tried that I haven't listed like escape characters without using a variable. I've come to the end of what I can figure out.
Is there a way to find and replace a line that has special characters with a line that has special characters? Any advice on how to do either the entire line or that second section with the quotes and space would be great. Thanks for your time.
Download JREPL.BAT written by Dave Benham which is a batch file / JScript hybrid to run a regular expression replace on a file using JScript and store it in same directory as the batch file below.
#echo off
call "%~dp0jrepl.bat" "file filename=\x22fffff" "network ipAdress=\x22172.24.55.32\x22 networkPort=\x229100" /XSEQ /F BCPrint.XML /O -
\x22 is " specified in hexadecimal form as an argument string can't contain a double quote character.
Passing arguments containing quotation marks is very difficult, because there are several places where those characters are recognised, so you had to build up individual escape sequences (^) every time.
The following example script fails for every call attempt:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define argument here:
set "_ARG=R2=172.24.55.32" networkPort="9100"
setlocal EnableDelayedExpansion
rem // Echo argument string to prove that variable is set:
echo(!_ARG!
echo/
rem // Call sub-routine with delayed expansion:
call :SUB "!_ARG!"
endlocal
rem // Call sub-routine with normal (immediate) expansion:
call :SUB "%_ARG%"
rem // Call sub-routine with double normal expansion (`call`):
call :SUB "%%_ARG%%"
endlocal
exit /B
:SUB
setlocal DisableDelayedExpansion
rem // Delayed expansion disabled during argument expansion:
set "ARG=%~1"
setlocal EnableDelayedExpansion
rem // Delayed expansion enabled for argument display (`echo`):
echo(!ARG!
endlocal
endlocal
exit /B
The easiest way to avoid such problems is to pass the variable name to the sub-routine rather than its value:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define argument here:
set "_ARG=R2=172.24.55.32" networkPort="9100"
setlocal EnableDelayedExpansion
rem // Echo argument string to prove that variable is set:
echo(!_ARG!
echo/
endlocal
rem // Call sub-routine and pass variable name:
call :SUB _ARG
endlocal
exit /B
:SUB
setlocal EnableDelayedExpansion
set "#ARG=%~1"
rem /* Normal expansion is used to get the variable name;
rem delayed expansion is used to read its value: */
echo(!%#ARG%!
endlocal
exit /B
To update a string in a file that contains "quotes", the following solution will work.
Create a file named replace_quot.ps1 in the same directory as your bat file. Place the following 2 lines in this script.
param([string]$Name_of_File)
(Get-Content $Name_of_File) -replace '"', '"' | set-content $Name_of_File
In your bat file enter the below lines
set File_Name=File Name.txt
set "Find_String=<file filename="fffff" />"
set "Replace_String=<network ipAdress="172.24.55.32" networkPort="9100" />"
set "Find_String=%Find_String:"="%"
set "Replace_String=%Replace_String:"="%"
powershell -command "(Get-Content '%File_Name%') -replace '[\x22]', '"' | Set-Content '%File_Name%'"
powershell -command "(Get-Content '%File_Name%') -replace '%Find_String%', '%Replace_String%' | Set-Content '%File_Name%'"
Powershell.exe -executionpolicy remotesigned -File replace_quot.ps1 -Name_of_File "%File_Name%"
Update the File_Name value to reflect your file name. The bat script will replace the quotes with their hex value in both the strings and the file. Next it will find the string that needs to be replaced and restore it to the desired value. Lastly it will call the replace_quot.ps1 script to update the file's quote hex values to the actual quote character.
Related
#ECHO OFF
if not exist "C:\test\test.txt" (
goto end
) else (
goto loop
)
:loop
echo Insert the name of the folder:
set /p name=<"C:\test\test.txt"
for /F "skip=1 delims=" %%a in ('type "C:\test\test.txt" ^& del "C:\test\test.txt"') do >> "C:\test\test.txt" echo %%a
echo Insert the name of the subfolder:
set /p name2=<"C:\test\test.txt"
for /F "skip=1 delims=" %%a in ('type "C:\test\test.txt" ^& del "C:\test\test.txt"') do >> "C:\test\test.txt" echo %%a
md "C:\test\%name%\testing %name2% 999"
move "C:\test\*%name2%*.txt" "C:\test\%name%\testing %name2% 999"
if exist "C:\test\test.txt" goto loop
:end
pause
exit
I want to make an "if" before the "echo Insert the name of the folder:" part, so that if the 1st line of the "test" text file contains any of this characters "\ / : * ? " < > |" it will delete those special characters
It looks like the file C:\test\test.txt should contain a set of folder names with on odd lines the main folder name and on even lines the subfolder name without path.
Microsoft explains on documentation page about Naming Files, Paths, and Namespaces which characters are not allowed in file/folder names.
If a line in the text file with the folder names contains an invalid character for a folder name, removing the character is not really the solution as it is most likely of no real help to move the files which contain the subfolder name into the subfolder.
I suggest following batch file for this task:
#echo off
if not exist "C:\test\test.txt" goto end
setlocal EnableExtensions DisableDelayedExpansion
set "MainFolderName="
set "SubfolderName="
for /F "usebackq eol=| delims=" %%I in ("C:\test\test.txt") do (
if not defined MainFolderName (
set "MainFolderName=%%~nxI"
) else (
set "SubfolderName=%%~nxI"
setlocal EnableDelayedExpansion
md "C:\test\!MainFolderName!\testing !SubfolderName! 999" 2>nul
if exist "C:\test\!MainFolderName!\testing !SubfolderName! 999\" (
move "C:\test\*!SubfolderName!*.txt" "C:\test\!MainFolderName!\testing !SubfolderName! 999\"
rd "C:\test\!MainFolderName!\testing !SubfolderName! 999\" 2>nul
)
endlocal
set "MainFolderName="
set "SubfolderName="
)
)
endlocal
:end
pause
I do not recommend using command exit in a batch file, especially not at end of the batch file. This is not useful and has just the disadvantage that debugging the batch file becomes a nightmare.
The command FOR with option /F and the other options in double quotes processes the text file line by line with skipping empty lines and lines starting with |.
The first string read from a line after last \ or / is assigned to variable MainFolderName.
The second string read from a line after last \ or / is assigned to variable SubfolderName.
Next delayed expansion is enabled to be able to reference the string values of the two environment variables.
The directory is created with suppressing an error output by redirecting it from handle STDERR to device NUL. An error is output if folder or subfolder name contains an invalid character.
The IF condition checks, if the directory really exists which is not the case on invalid character in one of the two directory names. So the command MOVE is executed only on valid folder names and command RD removes the directory on being still empty after moving the files.
Then the two environment variables are deleted before processing the next two lines from text file.
It would be possible to process the lines read from text file as described for example at:
How to verify if variable contains valid filename in Windows Batch
But I think, this is not really necessary. The best folder name verification is done by the file system itself. So removing all characters from a line read from a text file which are not allowed in a folder name is not really needed in this case.
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 /?
endlocal /?
for /?
goto /?
if /?
md /?
move /?
pause /?
rd /?
set /?
setlocal /?
Read also the Microsoft article about Using command redirection operators for an explanation of 2>nul.
You can remove special characters with a batch script using regex in vbscript : Demo Here
#echo off
Color 0A
Title How to verify if variable contains valid filename in Windows Batch
echo(
Echo Enter filename for this project
set /p "my_filename="
echo(
echo Before Removing the special char the filename is like this : "%my_filename%"
pause
echo(
Call :Remove_Special_Char "%my_filename%" NewFileName
echo After Removing the special char the filename becomes like this : "%NewFileName%"
pause & exit
::-----------------------------------------------------------------------------------
:Remove_Special_Char <String> <Variable to Set>
(
echo WScript.StdOut.WriteLine Search_Replace(Data^)
echo Function Search_Replace(Data^)
echo Dim strPattern, strReplace, strResult,oRegExp
echo Data = wscript.Arguments(0^)
echo strPattern = "[\\\/:*?\x22<>|]"
echo strReplace = ""
echo Set oRegExp = New RegExp
echo oRegExp.Global = True
echo oRegExp.IgnoreCase = True
echo oRegExp.Pattern = strPattern
echo strResult = oRegExp.Replace(Data,strReplace^)
echo Search_Replace = strResult
echo End Function
)>"%tmp%\%~n0.vbs"
#For /f "delims=" %%i in ('cscript //nologo "%tmp%\%~n0.vbs" "%~1"') do ( Set "%2=%%i" )
If Exist "%tmp%\%~n0.vbs" Del "%tmp%\%~n0.vbs"
Exit /B
::----------------------------------------------------------------------------------
I have whittled down a more complex CMD script to the essentials. It reads an input file line by line, unquotes it (if quoted) and writes it out to another CMD file.
The problem is that if the input file contains exclamation marks (! or bang) the character gets stripped out somewhere along the line.
Here is the CMD script, BANG1.CMD:
#echo off
setlocal ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
if exist bang2.cmd del bang2.cmd
for /f "tokens=*" %%a in (bang1.txt) do call :doit1 %%a
exit /b
:doit1
set P1=%1
if %P1%. EQU . exit /b
call :unquotex P1 %P1%
echo>>bang2.cmd echo P1:[%P1%]
exit /b
:unquotex
set X=%2
set Q=%X:~0,1%
if "!Q!" EQU ^""" SET X=!X:~1,-1!
set %1=%X%
exit /b
Here is the input file BANG1.TXT:
HelloWorld
"Hello World"
Hello!World
"Hello!World"
The resulting file BANG2.CMD ends up containing this:
echo P1:[HelloWorld]
echo P1:[Hello World]
echo P1:[HelloWorld]
echo P1:[HelloWorld]
The question is, what happened to the embedded bangs? I have tried with and without ENABLEDELAYEDEXPANSION. I have even tried escaping (^) the bangs in the input file, still with no luck.
Is there any way to preserve them?
Thanks.
The problem at all is delayed expansion here.
With delayed expansion, exclamation marks are used to expand variables, but when there is only one exclamation mark in a line it will be removed.
Specially in FOR /F loops delayed expansion is tricky to handle, as the expansion of the FOR parameter is directly affected by the delayed expansion. The only solution is to disable it temporarily.
The next problem is the CALL, you can't transfer content with CALL (without destroying it).
It's better to transfer the variable by reference (only the variable name) and then get the content in the called function.
The last problem in your code are the percent expansions, do not use them
when delayed expansion is enabled, as the delayed expansion is evaluated after the percent expansion an expanded line will be expanded a second time by the delayed expansion.
Sample.
Assume the content of var is Bang!
echo %var% expands to Bang! but then the delayed expansion will evaluate Bang! to Bang.
With echo !var! you simply get Bang!
#echo off
setlocal ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
if exist bang2.cmd del bang2.cmd
for /f "tokens=*" %%a in (bang1.txt) do (
setlocal DisableDelayedExpansion
set "line=%%a"
setlocal EnableDelayedExpansion
call :doit1 line
endlocal
endlocal
)
exit /b
:doit1
set "P1=!%1!"
if "!P1!" EQU "" exit /b
call :unquotex P1
echo>>bang2.cmd echo P1:[!P1!]
exit /b
:unquotex
set "param=!%~1!"
if "!param:~0,1!" == ^""" (
set "param=!param:~1,-1!"
)
set "%1=!param!"
exit /b
Like this :
#echo off
(for /f "delims=" %%a in ('type bang1.txt') do echo echo P1:[%%~a])>bang2.cmd
Try this:
#echo off
if exist bang2.cmd del bang2.cmd
for /f "tokens=*" %%a in (bang1.txt) do call :doit1 %%a
exit /b
:doit1
set "P1=%1"
if %P1%.==. exit /b
call :unquotex P1 %P1%
echo>>bang2.cmd echo P1:[%P1%]
exit /b
:unquotex
set "%1=%~2"
exit /b
Using parameters, you can get the version without quotes using %~1 instead of %1. If %1 contains "hello world" for example, then %~1 contains hello world. This allows for an easier unquoting mechanism, removing the need for delayed expansion.
I need help to split a file name or folder name into two parts.
All files in the subfolders use always the same naming convention:
mainfolder/artist - title/artist - title.ext
I started with creating a batch file which writes .nfo files (recursive) for video files. Everything is working fine, but I just can't find a way to echoing the part1 (artist) before delimiter - and after it to have part2 (title).
Here is what I have now:
#echo off
TITLE NFO creator Musicvideos
COLOR B
ECHO -------------------------------------------------------------------------
ECHO Create NFO for Musicvideos, all existing will be overwritten
ECHO Push key to Start
ECHO -------------------------------------------------------------------------
ECHO.
PAUSE > NUL
REM Ende Header
for /r %%a in (*.mkv *.avi *.mp4) do (
(
echo ^<?xml version="1.0" encoding="UTF-8" standalone="yes"?^>
echo ^<musicvideo^>
for %%b in ("%%~na") do echo ^ ^<title^>%%~b^</title^>
echo ^ ^<^!-- start test set attributes --^>
for %%b in ("%%~na") do echo ^ ^<path^>%~dp0^</path^>
for %%b in ("%%~na") do echo ^ ^<name^>%%~na^</name^>
for %%b in ("%%~na") do echo ^ ^<filenameandpath^>%%~a^</filenameandpath^>
for %%b in ("%%~na") do echo ^ ^<basepath^>%%~a^</basepath^>
for %%b in ("%%~na") do echo ^ ^<before^>^</before^>
REM for "tokens=1,2 delims= - " %%b in ("%%~na") do (
REM echo ^ ^<before^>^%%a</before^>
REM echo ^ ^<after^>^%%b</after^>
REM )
REM test end
echo ^ ^<rating^>^0.000000^</rating^>
echo ^ ^<userrating^>^8^</userrating^>
echo ^ ^<epbookmark^>^0.000000^</epbookmark^>
echo ^ ^<year^>^</year^>
echo ^ ^<track^>^-1^</track^>
echo ^ ^<album^>^</album^>
echo ^ ^<artist^>^</artist^>
echo ^ ^<genre^>^</genre^>
echo ^ ^<outline^>^</outline^>
echo ^ ^<plot^>^</plot^>
echo ^ ^<tagline^>^</tagline^>
echo ^ ^<thumb^>^</thumb^>
echo ^ ^<status^>^</status^>
echo ^ ^<studio^>^</studio^>
echo ^ ^<art^>
echo ^ ^<fanart^>%~dp0^%%~na^-fanart.jpg^</fanart^>
echo ^ ^<poster^>%~dp0^%%~na^-poster.jpg^</poster^>
echo ^ ^<artistthumb^>%~dp0^%%~na^-artistthumb.jpg^</artistthumb^>
echo ^ ^<banner^>%~dp0^%%~na^-banner.jpg^</banner^>
echo ^ ^<clearlogo^>%~dp0^%%~na^-clearlogo.png^</clearlogo^>
echo ^ ^<discart^>%~dp0^%%~na^-discart.png^</discart^>
echo ^ ^<landscape^>%~dp0^%%~na^-landscape.jpg^</landscape^>
echo ^ ^</art^>
echo ^</musicvideo^>
)>"%%~dpna.nfo"
)
ECHO.
ECHO Creatin is done
ECHO Push key to exit
ECHO.
PAUSE > NUL
This is the way I would do it:
#echo off
setlocal
for /R %%A in (*.mkv *.avi *.mp4) do call :SplitFileName "%%~nA"
goto :EOF
:SplitFileName
set "FileName=%~1"
set "Title=%FileName: - =" & set "Artist=%"
echo Artist=%Artist%
echo Title=%Title%
exit /B
Further details at Split string with string as delimiter. Pay attention to the comment at such an answer...
I cannot find a reason for all the loops for %%b in ("%%~na") do you are using, particularly because you are often not even using %%b in their bodies.
Anyway, splitting file names at a certain sub-string can be easily done by first replacing the sub-string by a single character that is forbidden in file names (like :, for example) and then using that character as a delimiter in a for /F loop:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Loop through all matching files recursively:
for /R %%a in (*.mkv *.avi *.mp4) do (
rem // Store file name in variable:
set "NAME=%%~na"
rem /* Toggle delayed expansion to be able to write a variable
rem in the same block of code without losing `!`: */
setlocal EnableDelayedExpansion
rem // Remove everything before first occurrence of and including ` - `:
echo Title: !NAME:* - =!
rem // Replace ` - ` by `:` and use the latter as delimiter:
for /F "tokens=1 delims=: eol=:" %%b in ("!NAME: - =:!") do (
endlocal
echo Artist: %%b
)
)
endlocal
exit /B
The above code assumes that both the artist and the title portions are not empty. The title part may include the sub-string - on its own.
This is an example on how to split a file name on first hyphen character.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
for /R %%A in (*.mkv *.avi *.mp4) do (
for /F "eol=| tokens=1* delims=-" %%B in ("%%~nA") do (
set "Artist=%%B"
set "Title=%%C"
setlocal EnableDelayedExpansion
if "!Artist:~-1!" == " " set "Artist=!Artist:~0,-1!"
if defined Title if "!Title:~0,1!" == " " set "Title=!Title:~1!"
echo Artist=!Artist!
if defined Title (echo Title=!Title!) else echo %%~na has no title.
endlocal
)
)
endlocal
The outer FOR loop searches recursive in current directory and all subdirectories for non-hidden files with file extension mkv, avi or mp4 and assigns the full qualified file name (file path + file name + file extension) of a found file to loop variable A.
The inner FOR loop interprets just the file name without path and file extension as string to process.
The end of line character is redefined from default ; which can be at beginning of a file name to | which no file name can ever contain to avoid that a file name starting by chance with a semicolon is ignored by the inner FOR loop.
By default FOR with option /F processing a double quoted string splits up the string into substrings using normal space and horizontal tab as string delimiters and assigns just first space/tab separated string to the specified loop variable. This string splitting behavior is modified with tokens=1* delims=- to split the file name string on hyphens instead of spaces/tabs with ignoring hyphens at beginning of file name and assigning the string up to first hyphen inside the file name to specified loop variable B and everything after first (sequence of) hyphen(s) to next loop variable according to ASCII table without any further string splitting which is loop variable C.
In other words on a file name like Artist - title the string Artist with a space at end is assigned to loop variable B and the string title with a space at beginning is assigned to loop variable C. The two unwanted spaces need to be removed. This can be done using string substitution. But this must be done using delayed expansion not yet enabled because of file names containing an exclamation mark should be also processed correct. For that reason delayed environment variable expansion is next enabled inside the inner FOR loop.
The first IF condition checks if last character of artist string is really a space character and runs the string substitution to remove this space at end of artist string if this condition is true.
The second IF condition first checks if an environment variable Title is defined at all because of the commands of inner FOR loop are also executed on file name does not contain a hyphen at all in which case %%C expands to an empty string and so environment variable Title is deleted from list of environment variables.
The nested third IF condition checks if first character of value of defined environment variable Title is a space and runs the string substitution to redefine Title without first character if this condition is true.
Now artist and title strings can be output before delayed expansion is disabled again by restoring previous environment. Please read this answer for details about the commands SETLOCAL and ENDLOCAL and what exactly happens on each execution of SETLOCAL and ENDLOCAL because of there is much more done than just enabling and disabling delayed environment variable expansion on each found file name.
Note: The title part can contain also the character sequence space, hyphen, space as well as one or more hyphens, but of course the artist string should not contain a hyphen as this character is interpreted as separator between artist and title independent on spaces existing around the hyphen or not. Windows command processor is not designed for enhanced string manipulations like other script interpreters. A different code would be necessary if the condition for splitting file name in artist and title must be space, hyphen, space and not just hyphen.
Other batch file code using - to split file name into artist and title strings:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
for /R %%A in (*.mkv *.avi *.mp4) do call :ProcessFileName "%%~nA"
endlocal
goto :EOF
:ProcessFileName
set "FileName=%~1"
set "Title=%FileName:* - =%"
setlocal EnableDelayedExpansion
set "Artist=!FileName: - %Title%=!"
echo Artist=!Artist!
echo Title=!Title!
endlocal
goto :EOF
But this solution using just string substitutions in a subroutine does not work on title string containing an equal sign used as delimiter on string substitution. It is also much slower in comparison to above batch file solution on processing a large amount of file names.
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.
call /?
echo /?
endlocal /?
for /?
goto /?
if /?
set /?
setlocal /?
See also Where does GOTO :EOF return to?
Assuming your title always have only a single - splitting artist from title:
#echo off
setlocal enabledelayedexpansion
for /r %%a in (*.mkv *.avi *.mp4) do (
set "fname=%%~na"
set "fname=!fname: - =-!"
for /f "tokens=1,2 delims=-" %%i in ("!fname!") do (
echo The artist is %%i
echo The title is %%j
echo The extension is %%~xa
)
)
We replace the space-space with a - and then delimit on it.
I am using a program called "Easy Context Menu" which allows me to create customized context menus when you right click in File Explorer. I wanted to create my own Context Menu item that allows me to copy a file and it's parenting folder structure into another directory, excluding all of the files in the parented folders.
Example:
target file path: C:\the\big\foo\bar.txt
destination path: D:\cow
I want the file 'bar.txt' and it's parenting folders up until 'big' to be copied into the directory 'cow' without the other files in 'big' or 'foo'. The end result should look like:
D:\cow\big\foo\bar.txt
'big' should only contain 'foo' and 'foo' should only contain 'bar.txt'. The program allows me to send the file as a parameter to a file of my choice, in this case a batch file.
I have gotten stuck at the for loop in the ':main" subroutine. It will only print the whole path of the selected file and ignores the delimiter. 'countA' is returned as '1' after the loop and it would have printed the whole path minus drive letter. I do not understand why the for loop is ignoring the delimiter and not separating the path into separate folder names. The reason I am tying to do this is so the user can choose at which point the parented folders should be copied over. This is my first time writing actual code in batch so I still don't completely understand 'setlocal' and a few other things.
I have tried changing the tokens and I am able to choose the individual sections in between the slashes, but I am never able to capture the entire string as separate chunks. I have also changed the delimiter and got the same issue.
#echo off
setlocal
set _target=""
set _dest=""
if not exist %1 goto:error_no_path
set _target=%~pnx1
echo %_target%
goto :dest_selct
:dest_selct
echo select destination folder
pause
set "psCommand="(new-object -COM 'Shell.Application')^
.BrowseForFolder(0,'Please choose destination folder.',0,0).self.path""
for /f "usebackq delims=" %%I in (`powershell %psCommand%`) do set "folder=%%I"
setlocal enabledelayedexpansion
echo You chose %folder%
choice /c ync /n /m "Is this correct? ([Y]es, [N]o, [C]ancel)"
if %ERRORLEVEL% EQU 3 goto:eof
if %ERRORLEVEL% EQU 2 goto:dest_selct
if %ERRORLEVEL% EQU 1 (set _dest="%folder%")&&(goto:main)
:error_no_path
ECHO No file path/name
pause
GOTO:eof
:main
echo main
set _countA=0
echo count starts at %_countA%
for /f "tokens=* delims=\" %%F in ("%_target%") do (
echo token %_countA% is %%F
echo count is %_countA%
set var!_countA!=%%F
set /a countA+=1
)
echo var1: %var1%
echo count is now %countA%
pause
I should have more than one variable at the end, each being the name of a folder that parents the target file and the count should be however many parent folders there are, plus the file itself. What I am actually getting is one variable and it contains the whole target path still.
The for loop does not delimit as you have tokens=*,
which gets all tokens as one token. Leading delimiters are stripped.
Only var0 has a value, not the echoed var1.
To split _target by path segments, use:
for %%F in ("%_target:\=" "%") do echo %%~F
Another issue you have is if you choose n at the choice prompt,
the goto:dest_selct will cause setlocal enabledelayedexpansion
to execute again. Since you are not using endlocal, then the
local environment is recursing without ending. Sometimes endlocal
is implied, so you may not need to use it, though not in this case.
Try this fix:
#echo off
setlocal
set "_target="
set "_dest="
if not exist "%~1" goto:error_no_path
set "_target=%~pnx1"
echo %_target%
goto :dest_selct
:dest_selct
echo select destination folder
pause
set "psCommand="(new-object -COM 'Shell.Application')^
.BrowseForFolder(0,'Please choose destination folder.',0,0).self.path""
for /f "usebackq delims=" %%I in (`powershell %psCommand%`) do set "folder=%%I"
echo You chose %folder%
choice /c ync /n /m "Is this correct? ([Y]es, [N]o, [C]ancel)"
if %ERRORLEVEL% EQU 3 goto:eof
if %ERRORLEVEL% EQU 2 goto:dest_selct
if %ERRORLEVEL% EQU 1 (set "_dest=%folder%") && goto:main
:error_no_path
ECHO No file path\name
pause
GOTO:eof
:main
setlocal enabledelayedexpansion
echo main
set "countA=0"
echo count starts at %countA%
rem Remove leading backslash if exist.
if "%_target:~,1%" == "\" set "_target=%_target:~1%"
rem Split into path segments.
for %%F in ("%_target:\=" "%") do (
echo token !countA! is %%~F
echo count is !countA!
set "var!countA!=%%~F"
set /a countA+=1
)
echo var1: %var1%
rem Echo all variables starting with var and their values.
set var
echo count is now %countA%
endlocal
pause
Adjusted double quoting.
Changed instances of variable name of _countA to countA to match.
set var to show all variables starting with var for debugging.
for loop now spilts on path segments.
Moved setlocal enabledelayedexpansion out of label loop.
Fix 1st argument check if not defined.
Refer to set /? about variable substitution i.e. "%_target:\=" "%", which replaces \ with " ".
Set a=%~1
echo %a%>>"C:\file.txt"
Im calling the script via cmd
"C:\batch.bat" "$Multi Line String$"
The output file is only having the text till the first line break.
Please Help
Obviously, there is a way to to pass a value with multi-line text in a Batch file parameter.
But it is a little bit complex and currently there isn't a way to get the content out of the parameter in a safe way for any possible content.
A simple demonstration
Producer.bat - puts "Line1<line feed>Line2" into %1
#echo off
setlocal EnableDelayedExpansion
(set LF=^
%=empty=%
)
set ^"pVar=^^^^^"Line1^^^^!LF!!LF!line2""
call Receiver.bat %%pVar%% #
Receiver.bat
#echo off
prompt :
#echo on
(
#exit /b
REM # %~1 #
)
Output
:(
REM # Line1
line2 #
)
To fetch this, the solution from How to receive even the strangest command line parameters? can be extended.
The problem is, that it only works with simple content, but there exists content which can't be received without producing parser errors.
Like: "Line1<line feed>Line2"
Therefore I would recommend Aacini's solution
Edit: Improved samples
A batch file to call another batch file with multi line variables.
#echo off
setlocal EnableDelayedExpansion
(set \n=^
%=empty=%
)
set "var=Line1!\n!line2!\n!line3!\n!line4"
call :callBatch var
exit /b
:callBatch
setlocal EnableDelayedExpansion
set "prepVar=!%~1!"
for %%L in ("!\n!") DO (
for %%b in ("^=^^" "&=^&" "<=^<" ">=^>" "|=^|") do set "prepVar=!prepVar:%%~b!"
set "prepVar=^^"!prepVar:%%~L=^^%%~L%%~L!""
)
rem echo !prepVar!-
call receiver.bat %%prepVar%%
exit /b
And a receiver.bat that accepts and parses a multi line value from %1.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
prompt :
(
#echo on
for %%a in (4) do (
#goto :next
rem # %~1#
)
) > param.tmp
:next
#echo off
endlocal
setlocal DisableDelayedExpansion
set lineNo=0
(
for /F "skip=3 delims=" %%L in (param.tmp) do (
set /a lineNo+=1
set "line=%%L"
setlocal EnableDelayedExpansion
if !lineNo! EQU 1 (
set "line=!line:*# =!"
set "line=!line:~,-2!"
) ELSE IF "!line!"==") " (
goto :exit
) ELSE (
set "line=!line:* =!"
set "line=!line:~,-1!"
)
(echo(!line!)
endlocal
)
) > file.txt
:exit
type file.txt
Edit2: Unsolved problems
It's possible to fetch any content in line1, but all other lines have restrictions for their content.
The content has to be "parse conform", that means that a multi line parameter with this content fails
Line1
If a=
It's because the first line can be secured with REM, but all other lines have to be valid lines.
Carets are only visible in line1 (with the REM technic).
And lines beginning with # are empty and : are removed completly.
As I said in a comment, there is no way to pass a value with multi-line text in a Batch file parameter. You should pass the name of the multi-line text variable instead and expand it using DelayedExpansion. This is an example of such a method:
#echo off
setlocal EnableDelayedExpansion
rem Create a variable with just LineFeed
set LF=^
%Don't remove%
%these two lines%
rem Create the variable with multi-line text
set "MultiLineString=Line1: Test.!LF!Line2: Testing.!LF!Line3: Tested."
rem Call the Batch script passing *THE NAME* of the variable
cmd /C batch.bat MultiLineString
And this is your same batch.bat script with the changes I suggested:
#echo off
setlocal EnableDelayedExpansion
Set a=%~1
echo !%a%!> file.txt
echo File created:
type file.txt
Output:
File created:
Line1: Test.
Line2: Testing.
Line3: Tested.
This method works as long as the calling program have stored the multi-line string in an environment variable, that is the only type of variables that can be accessed by a Batch file. For this reason, it is important for us to know how you created the multi-line string; otherwise, we have no means to solve this problem...
EDIT: New method added
After I read the improvements to jeb's answer I must admit that I was wrong: it is possible to pass the value of a multi-line variable in a Batch file parameter. This is my own version of such a method:
#echo off
setlocal EnableDelayedExpansion
rem Create a variable with just LineFeed
(set LF=^
%empty line%
)
rem Create the variable with multi-line text
set "MultiLineString=Line1: <Test>.!LF!Line2: |Testing|.!LF!Line3: &Tested&."
rem Prepare the multi-line variable and call the receiver Batch file
call :callBatch
echo Back to caller...
goto :EOF
:callBatch
rem Prepare the multi-line variable
set "lines="
for /F "delims=" %%a in ("!MultiLineString!") do (
if not defined lines (
set "lines=%%a "
) else (
set "line=%%a"
for %%b in ("&=^&" "<=^<" ">=^>" "|=^|") do set "line=!line:%%~b!"
set "lines=!lines!!LF!!line!"
)
)
REM echo [!lines!]
rem Call the receiver Batch file
(batch.bat "!lines!")
exit /B
And this is the new version of the companion batch.bat file:
#echo off
setlocal EnableDelayedExpansion
(
prompt $S
echo on
for %%a in (_) do if a==b (
%1
)
) > param.tmp
#echo off
(for /F "skip=2 delims=" %%a in (param.tmp) do if "%%a" neq ")" (
set "line=%%a"
echo(!line:~1,-2!
)) > file.txt
del param.tmp
echo File created:
echo/
type file.txt
This version correctly manage most special Batch characters, excepting exclamation-mark. This is the output:
File created:
Line1: <Test>.
Line2: |Testing|.
Line3: &Tested&.
Back to caller...
PS - This method will not work if the receiver batch.bat file is executed from outside another batch file, that is, via the standard cmd /C batch.bat "$Multi Line String$" method.