Programmatically update a folder in PATH from a batch file - windows

(I do not think there is an exact similar match for this question)
I need to make a batch file for Windows (XP and 7) that will:
install Python
check for any instance of a previous python folder in PATH (typically C:/Python2x, C:/Python3x,C:/Python2x/Scripts, C:/Python3x/Scripts)
remove all these folders from PATHs permanently
add C:/Python/Scripts and C:/Python permanently in PATH in System/Environment variables
Is this something fairly doable using batch scripts ? I have read in a previous question that I can use setx to set the variable permanently but I am struggling with the matching part.

#ECHO OFF
SETLOCAL
SET "newpython=C:\Python;C:\Python\Scripts"
SET "newpath="
:temploop
SET tempfile=%random%%random%%random%
IF EXIST "%temp%\%tempfile%*" GOTO temploop
SET "tempfile=%temp%\%tempfile%"
CALL :showpath >"%tempfile%"
SET "response=x"
FOR /f "delims=" %%p IN ('type "%tempfile%"') DO (
CALL :addsegment "%%p"
IF NOT DEFINED response DEL "%tempfile%"&GOTO :EOF
)
SET "newpath=%newpython%%newpath%
DEL "%tempfile%"
CALL :getresp "Apply new PATH=%newpath% [Y/N/Q]?"
IF /i "%response%"=="Y" ECHO SETX PATH "%newpath%"
GOTO :EOF
:addsegment
SET "segment=%~1"
IF /i "%segment%"=="%segment:python=%" SET response=N&GOTO nosdel
CALL :getresp "Delete %segment% from path [Y/N/Q]?"
:nosdel
IF /i "%response%"=="N" SET "newpath=%newpath%;%segment%"
GOTO :eof
:getresp
SET "response="
SET /p "response=%~1 "
IF /i "%response%"=="Y" GOTO :eof
IF /i "%response%"=="Q" SET "response="&GOTO :eof
IF /i NOT "%response%"=="N" ECHO Please respond Y N or Q to quit&GOTO getresp
GOTO :eof
:showpath
ECHO(%path:;=&ECHO(%
GOTO :eof
Installation of python - well, that's up to you. Don't know where it will be installed from, or to - yor job to find out.
Assuming the new directories are concatenated and separated by ; in variable newpython and noting that directory-segment separators are \ not / (which is used for switches) then the above:
establishes a temporary file
analyses the path, presenting each segment to be deleted or no
re-assembles the path and prefixes the newpython directories.
Asks whether to apply the changes.
I don't know which options you require for the setx, so the command is simply ECHOed. You'd need to remove the ECHO from the SETX line to activate the setting of the path variable.
Note also that SETX does not set the target variable in existing or the current CMD instances - only those created in the future.

#echo off
setlocal
setlocal enableDelayedExpansion
set "_PATH_=%PATH:;=";"%"
set "new_path="
for %%P in (%_PATH_%) do (
set "path_element=%%~P"
rem :: check if the path element contains "/Python"
if "!path_element!" equ "!path_element:/Python=!" (
set new_path="!path_element!";!new_path!
)
)
rem :: add the new directories to path
set new_path=%new_path%;C:/Python/Scripts;C:/Python;
endlocal & new_path=%new_path%
rem :: set the new path
set path=%new_path%
rem :: import the path to the registry
(
echo Windows Registry Editor Version 5.00
echo [HKEY_CURRENT_USER\Environment]
echo "PATH"=%new_path%
) >"%temp%\~path.reg"
REGEDIT /S "%temp%\~path.reg"
del "%temp%\~path.reg" /q /f
endlocal
endlocal
may not work if there are special symbols in the path like !

Related

Batch get relative path from absolute path

How can I convert an absolute path to a relative path in batch? I have an absolute path to a directory A and a reference directory B, and I need the path to A relative to B. As example, the following batch script should print ..\other\somedir\.
setlocal enabledelayedexpansion
set referencePath=C:\Users\xyz\project\
set absolutePath=C:\Users\xyz\other\somedir\
set relativePath=...
echo %relativePath%
I tried relativePath=!absolutePath:%referencePath%=!, but this yields the absolute path C:\Users\xyz\other\somedir\.
I need something similar to the python function os.path.relpath:
>>> os.path.relpath("C:\\Users\\xyz\\other\\somedir", "C:\\Users\\xyz\\project\\")
"..\\other\\somedir"
I need this because I have a batch file with command line arguments similar to the above file names. This batch file creates another batch file startup.bat which sets some environment variables and starts an application. The startup.bat may be called over network, so I have to use relative paths. With absolute paths, the environment variables would point to the files on the wrong machine.
Here is a quick'n'dirty hack:
#echo off
setlocal enabledelayedexpansion
set referencePath=C:\Users\xyz\project\
set absolutePath=C:\Users\xyz\other\somedir\
set relativePath=
:LOOP
for /F "tokens=1 delims=\" %%a in ("%referencePath%") do (set ref=%%a)
for /F "tokens=1 delims=\" %%a in ("%absolutePath%") do (set rel=%%a)
if /i !ref!==!rel! (
set referencePath=!referencePath:%ref%\=!
set absolutePath=!absolutePath:%rel%\=!
goto LOOP
)
:RELLOOP
for /F "tokens=1 delims=\" %%a in ("%absolutePath%") do (
set absolutePath=!absolutePath:%%a\=!
set relativePath=!relativePath!..\
)
if not "%absolutePath%"=="" goto RELLOOP
set complRelPath=%relativePath%%referencePath%
echo !complRelPath!
This won't give you propper output if the folders are on different drives so you'll have to handle this special case yourself.
EDIT (comment): Well, this can't be that hard that you couldn't figure it out yourself. If / and \ are mixed (which is a bad idea - we are on Windows! Windows means \ in paths, UNIX etc. means / in paths) you should replace / by :
SET referencePath=%referencePath:/=\%
SET absolutePath=%absolutePath:/=\%
If the paths are equal, you have nothing to do so:
IF %referencePath%==%absolutePath% (
SET complRelPath=.\
GOTO WHATEVER
)
#ECHO OFF
setlocal enabledelayedexpansion
set referencePath=C:\Users\xyz\project\
set absolutePath=C:\Users\xyz\other\somedir\
FOR %%a IN ("%absolutepath%") DO FOR %%r IN ("%referencepath%.") DO (
SET "abspath=%%~pa"
SET "relativepath=!abspath:%%~pr=..\!"
)
echo %relativePath%
GOTO :EOF
It would be of assistance if you were to tell us what your desired output is. Telling us what the output of your current code is, and that implicitly that's not what you expect, and then how to obtain something using some other platform is not particularly helpful.
The problem is that you are attempting to replace a string containing a colon within a string contining a colon. cmd` gets confused as it doesn't know which colon of the three is which.
This solution is resticted, since it assumes that the part of the path to be removed is exactly the parent directory of referencepath. In the absence of more information, it's as far as I'm prepared to guess...
…and an example which leverages PowerShell:
#Echo Off
Set "referencePath=C:\Users\xyz\project"
Set "absolutePath=C:\Users\xyz\other\somedir"
Set "relativePath="
Set "_="
If /I Not "%CD%"=="%referencePath%" (Set "_=T"
PushD "%referencePath%" 2>Nul || Exit /B)
For /F "Delims=" %%A In ('
PowerShell -C "Resolve-Path -LiteralPath '%absolutePath%' -Relative"
') Do Set "relativePath=%%A"
If Defined _ PopD
If Defined relativePath Echo %relativePath%
Pause
This obviously only works with actual existing paths
Well, usually I strongly recommend not to do string manipulation on file or directory paths, because it is quite prone to failures. But for a task like this, which does not rely on existing paths, there appears no way around.
Anyway, for doing so in a reliable fashion, the following issues must be considered:
Windows uses case-insensitive paths, so do all path comparisons in such manner as well!
Avoid sub-string substitution, because it is troublesome with =-signs and a few other characters!
Ensure to properly resolve the provided paths, using the ~f-modifier of for-loop meta-variables! This ensures that the input paths are really absolute, they do not contain doubled separators (\\), and there are no sequences with . and .. (like abc\..\.\def, which is equivalent to def), that make comparison difficult.
Regard that paths may be provided in an ugly way, like with trailing \ or \., bad quotation (like abc\"def ghi"\jkl), or using wrong path separators (/ instead of \, which is the Windows standard).
Alright, so let us turn to a script that I wrote for deriving the common path and the relative path between two absolute paths, regarding all of the said items (see the rem comments):
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "referencePath=C:\Users\"xyz"\dummy\..\.\project"
set "absolutePath=C:\Users\"xyz"\other\\somedir\"
rem /* At first resolve input paths, including correction of bad quotes and wrong separators,
rem and avoidance of trailing separators (`\`) and also other unwanted suffixes (`\.`): */
set "referencePath=%referencePath:"=%"
set "absolutePath=%absolutePath:"=%"
for %%P in ("%referencePath:/=\%") do for %%Q in ("%%~fP.") do set "referencePath=%%~fQ"
for %%P in ("%absolutePath:/=\%") do for %%Q in ("%%~fP.") do set "absolutePath=%%~fQ"
rem // Initially clean up (pseudo-)array variables:
for /F "delims==" %%V in ('2^> nul ^(set "$ref[" ^& set "$abs["^)') do set "%%V="
rem // Split paths into their elements and store them in arrays:
set /A "#ref=0" & for %%J in ("%referencePath:\=" "%") do if not "%%~J"=="" (
set /A "#ref+=1" & setlocal EnableDelayedExpansion
for %%I in (!#ref!) do endlocal & set "$ref[%%I]=%%~J"
)
set /A "#abs=0" & for %%J in ("%absolutePath:\=" "%") do if not "%%~J"=="" (
set /A "#abs+=1" & setlocal EnableDelayedExpansion
for %%I in (!#abs!) do endlocal & set "$abs[%%I]=%%~J"
)
rem /* Determine the common root path by comparing and rejoining the array elements;
rem also build the relative path herein: */
set "commonPath=\" & set "relativePath=." & set "flag=#" & set /A "#cmn=#ref+1"
for /L %%I in (1,1,%#abs%) do (
if defined flag (
set "flag=" & if defined $ref[%%I] (
setlocal EnableDelayedExpansion
if /I "!$abs[%%I]!"=="!$ref[%%I]!" for %%J in ("!commonPath!\!$ref[%%I]!") do (
endlocal & set "commonPath=%%~J" & set "flag=#" & set /A "#cmn=%%I+1"
)
)
)
if not defined flag (
setlocal EnableDelayedExpansion & for %%J in ("!relativePath!\!$abs[%%I]!") do (
endlocal & set "relativePath=%%~J"
)
)
)
rem // Complete the relative path by preceding enough level-up (`..`) items:
for /L %%I in (%#cmn%,1,%#ref%) do (
setlocal EnableDelayedExpansion & for %%J in (".\.!relativePath!") do (
endlocal & set "relativePath=%%~J"
)
)
set "relativePath=%relativePath:*\=%" & if "%commonPath:~-1%"==":" set "commonPath=%commonPath%\"
set "commonPath=%commonPath:~2%" & if not defined commonPath set "relativePath=%absolutePath%"
rem // Eventually return results:
set "referencePath"
set "absolutePath"
set "commonPath" 2> nul || echo commonPath=
set "relativePath"
endlocal
exit /B
Note that this approach does not support UNC paths.
Though I understand exactly what you want to do, I do not like this method.
Instead, why not use your environment path, where we will search for the relevant file in path.
#echo off
for %%g in ("bin\startup-batch.cmd") do #set "pathTobat=%%~$PATH:g"
echo "%pathTobat%"
The solution of #MichaelS is good, but has problems:
If subdirectories have same or similar names as their parents, it doesn't work.
setlocal without endlocal
Same-path condition not in script
No command-line-interface (CLI)
Improved version follows: (save as setComRelPath.bat) (you need strlen.cmd from https://ss64.com/nt/syntax-strlen.html)
#echo off
rem Usage: call setCompRelPath.bat C:\A\B C:\i\am\here
rem echo "%compRelPath%"
rem
rem Alternernatively,
rem set position_target=D:\44_Projekte\imagine\
rem set position_now=%CD%\
rem call setCompRelPath.bat
rem echo "%compRelPath%"
if not [%1]==[] set position_target=%1
if not [%2]==[] set position_now=%2
set __absolutePath=%position_now%
set __referencePath=%position_target%
if %__referencePath%==%__absolutePath% (
set complRelPath=.\
exit /b
)
rem echo __referencePath=%__referencePath%
rem echo __absolutePath=%__absolutePath%
set relativePath=
:LOOP
for /F "tokens=1 delims=\" %%a in ("%__referencePath%") do (set ref=%%a)
for /F "tokens=1 delims=\" %%a in ("%__absolutePath%") do (set rel=%%a)
if /i not %ref%==%rel% goto RELLOOP
call strlen.cmd "x%ref%" _strlen
call set __referencePath=%%__referencePath:~%_strlen%%%
call set __absolutePath=%%__absolutePath:~%_strlen%%%
rem echo abs^> %__absolutePath%
goto LOOP
:RELLOOP
for /F "tokens=1 delims=\" %%a in ("%__absolutePath%") do call :SUB_relpath %%a
if not "%__absolutePath%"=="" goto RELLOOP
goto FIN
:SUB_relpath
set ARG=%1
call strlen.cmd "x%ARG%" _strlen
rem echo abs: %__absolutePath% // ARG=%ARG% // rel: %relativePath%
call set __absolutePath=%%__absolutePath:~%_strlen%%%
set relativePath=%relativePath%..\
exit /b
:FIN
set compRelPath=%relativePath%%__referencePath%
If you want to see it functioning, uncomment the echo lines

Delete specific subfolder under unknown folders from anywhere

I have a folder named G:\Project\Projects
This folder has some(about 20) unknown subfolders. Some of the subfolders have a folder named SSS and a file called deleteme.doc
I want to delete all these SSS folders(with contents) and deleteme.doc files using a batch file or powershell file. I do not want to delete these files or folder deeply nested into the subfolders, just those who are directly under subfolder.
For example I want to delete this folder
G:\Project\Projects\proj 1\sss
but not these 3-
G:\Project\Projects\proj 1\other\sss
G:\Project\Projects\sss
G:\Project\Projects\proj 1\sss (it is a file)
The thing is I want to simply double click-run the batch/powershell file from anywhere, possibly from another partition or drive (or even network computer).
I tried RMDIR G:\Project\Projects\*\SSS it didn't seem to be working.
Please explain your answer if possible, also show me commands for both recycle bin delete and parma delete. Though I think sending to recycle bin is not possible without powershell.
Administrator privilege is not necessary to delete folders in non-system partition right?
Windows 10. powershell version 5
#ECHO Off
SETLOCAL
:: remove variables starting $
FOR /F "delims==" %%a In ('set $ 2^>Nul') DO SET "%%a="
:: set defaults
SET "$dirname=sss"
SET "$filename=deleteme.doc"
SET "$from=c:\wherever\you\like"
:: analyse command line
:: syntax file="filename" " dir=dirname" from="dirname" (/switches)
:: each is optional. default is as established above
:: /d - delete directory only if it is completely empty
:: /dn - delete directory only if it is not completely empty
:: /ds - delete directory only if it is completely empty or if no files in its subdirectory tree
:: /fe - delete file if empty
:: /fn - delete file if not empty
:: /b - both file and directory must exist to make deletions
::
FOR %%a IN (%*) DO IF DEFINED $twopart (CALL :parttwo %%a) ELSE (
SET "$processed="
IF /i "%%a"=="dir" SET "$processed=Y"&SET "$twopart=$dirname"
IF /i "%%a"=="file" SET "$processed=Y"&SET "$twopart=$filename"
IF /i "%%a"=="from" SET "$processed=Y"&SET "$twopart=$from"
IF /i "%%a"=="/b" CALL :setswitch b
IF /i "%%a"=="/d" CALL :setswitch d
IF /i "%%a"=="/dn" CALL :setswitch dn
IF /i "%%a"=="/ds" CALL :setswitch ds
IF /i "%%a"=="/fe" CALL :setswitch fe
IF /i "%%a"=="/fn" CALL :setswitch fn
IF NOT DEFINED $processed CALL :extras %%a
)
IF DEFINED $extras ECHO(%$extras% NOT understood)&pause&GOTO :EOF
:: resolve switch compatibility
IF DEFINED $d IF DEFINED $dn SET "$incompatible=/d /dn"
IF DEFINED $fe IF DEFINED $fn SET "$incompatible=%$incompatible% /f /fn"
IF DEFINED $incompatible ECHO(%$incompatible% incompatible)&pause&GOTO :EOF
:: if $from is set, make it quoted.
IF DEFINED $from SET "$from="%$from:"=%""
:: Now search for the directory
FOR /d /r %$from% %%a IN ("%$dirname%") DO IF EXIST "%%a\." (
SET "$victim=%%a"
CALL :deldir
)
IF NOT DEFINED $b FOR /r %$from% %%a IN ("%$filename%") DO IF EXIST "%%a" (
SET "$victim=%%~dpa%$filename%"
CALL :delfile
)
GOTO :eof
:: delete the victim directory if all other conditions are met
:: take care of file as well if required.
:deldir
SET "$victim=%$victim:"=%"
IF DEFINED $b IF NOT EXIST "%$victim%\..\%$filename%" GOTO :eof
:: if the directory has any contents and "/d" was specified, skip deletion
IF DEFINED $d (
FOR /f %%z IN ('dir /b "%$victim%" 2^>nul') DO GOTO :eof
)
:: if the directory has files in its subtree and "/ds" was specified, skip deletion
IF DEFINED $ds (
FOR /f %%z IN ('dir /s /b /a-d "%$victim%" 2^>nul') DO GOTO :eof
)
:: if the directory has no files and none in its subtree and "/dn" was specified, don't skip deletion
IF DEFINED $dn (
FOR /f %%z IN ('dir /b "%$victim%" 2^>nul') DO GOTO deldir1
GOTO :eof
)
:: delete the directory
:deldir1
ECHO(DEL /s /f /q "%$victim%"
SET "$victim=%$victim%\..\%$filename%"
IF EXIST "%$victim%" GOTO delfile
GOTO :eof
:delfile
FOR %%z IN ("%$victim%") DO (
IF DEFINED $fn IF "%%~zz"=="0" GOTO :EOF
IF DEFINED $fe IF "%%~zz" neq "0" GOTO :EOF
)
ECHO(DEL /f /q "%$victim%"
GOTO :eof
:parttwo
SET "%$twopart%=%~1"
SET "$twopart="
GOTO :eof
:extras
SET "$extras=%$extras% %~1"
GOTO :eof
:setswitch
SET "$processed=Y"
SET "$%1=Y"
SHIFT
IF "%1" neq "" GOTO setswitch
GOTO :EOF
Here's a general solution which you'd need to test before unleashing.
The required RD commands are merely ECHOed for testing purposes. After you've verified that the commands are correct, change ECHO(RD to RD to actually delete the directories.
The required DEL commands are merely ECHOed for testing purposes. After you've verified that the commands are correct, change ECHO(DEL to DEL to actually delete the files.
The first section analyses the parameters ans switches supplied to the program.
The defaults are shown and need to be adjusted to suit your circumstances.
The command may be run as
*thisname* from=c:\new\location dir=xxx file=hello.txt /d
where from specifies where the tree-walk begins, fie the filename to be located and dir the directoryname to delete.
The switches are also listed.
The program simply examines the command line for elements of interest and sets values as required.
We then traverse the tree, looking for the directoryname and then make decisions depending on the switch-settings.
Then traverse the tree again, looking for the filename.
Something like
for /f %A in ('dir "c:\somewhere\deleteme.doc"') Do echo rd "%~dpA"
Administrator privilege is not necessary to delete folders in non-system partition right?
How would we know what permissions you have. Type icacls c:\somewhere to see.
You need to know your problem.
I created a short powershell solution using this-
$targetName='ss s';
foreach ($file in Get-Childitem "G:\Project\Projects\" )
{
$fname=$file.name;
if (Test-Path "G:\Project\Projects\$fname\$targetName\")
{
$shell = new-object -comobject "Shell.Application"
$item = $shell.Namespace(0).ParseName("G:\Project\Projects\$fname\$targetName")
$item.InvokeVerb("delete")
}
}
This powershell script sends that folder to recycle bin after confirmation popup. (this won't send any file named 'ss s')
this seems to be working for batch file script-
set "b=ss s"
for /d %%a in (*) do IF EXIST "%%a\%b%\*" (rmdir /s "%%a\%b%")
if the targetfolder was named "$ss s" then you have to set variables as "b=/$ss s"

How do I specify the file directory for it to run in task scheduler?

I added this batch file into the task scheduler but it couldn't be load and it keeps looping and most probably it is because of the file directory that I have wrote. But if I was to run this batch file from the local directory instead, this batch file can be run. So, how do I change the file directory in the if statement to allow it to run it in the task scheduler? What path do I need to use?
#ECHO Off
SETLOCAL
:CheckForFile
if exist "MATS\NX_EXTR.txt" %AND% if exist "MTA\NX_EXTR.txt" goto FileExists
timeout 1
goto :CheckForFile
:FileExists
FOR /f "tokens=1*delims=" %%a IN (MATS\NX_EXTR.txt) DO SET "L2a=%%a"&SET "L1a="&GOTO nextstep
:nextstep
(
FOR /f "delims=" %%a IN (MTA\NX_EXTR.txt) DO (
IF DEFINED L1a (ECHO(%%a) ELSE (SET "L1a=%%a"&CALL :sumlines)
)
FOR /f "skip=1delims=" %%a IN (MATS\NX_EXTR.txt) DO (ECHO(%%a)
) >C.txt
TYPE C.txt
GOTO :EOF
:sumlines
SET /a L2a=1%L1a:~10%+1%L2a:~10%
ECHO(%L1a:~0,10%%L2a:~1%
GOTO :eof
Seems familar somehow...
if exist "MATS\NX_EXTR.txt" %AND% if exist "MTA\NX_EXTR.txt" goto FileExists
First, %AND% is not required. if exist filename if exist filename2 something parameters
will execute something (even if it's a goto) when both files exist.
This will probably work as it stands because and is undefined and thus %and% is resolved to an empty string.
In all probability, the problem is that MATS\NX_EXTR.txt is a relative pathname to a file - it's short for .\MATS\NX_EXTR.txt and hence finding the file depends on the current directory.
Your fix is to either
change to he appropriate directory within the batch by executing
cd "d:\parent\of"
where d:\parent of" is the parent directoru ofMATS\NX_EXTR.txt(ie. the **absolute filepath** isd:\parent of\MATS\NX_EXTR.txt`
OR
replace MATS\NX_EXTR.txt with the absolute filepath (ie. d:\parent of\MATS\NX_EXTR.txt)
Naturally, you'd needto repeat this for each of the filenames you use, including C.txt
In order to keep from having to maintain this batch, you could also use
set "file1=d:\parent of\MATS\NX_EXTR.txt"
at the start of the program, and then use %file1% to refer to the file. Obviously, repeat for all files - and possibly even change the symbolic name file1 to something that may carry some meaning (newdatafile) for instance)
Revision example (I've no idea of your actual absolute filenames)
#ECHO Off
SETLOCAL
set "file1=c:\wherever\MATS\NX_EXTR.txt"
set "file2=c:\wherever\MTA\NX_EXTR.txt"
set "file3=c:\wherever\C.txt"
:CheckForFile
if exist "%file1%" if exist "%file2%" goto FileExists
timeout 1
goto :CheckForFile
:FileExists
FOR /f "tokens=1*delims=" %%a IN (%file1%) DO SET "L2a=%%a"&SET "L1a="&GOTO nextstep
:nextstep
(
FOR /f "delims=" %%a IN (%file2%) DO (
IF DEFINED L1a (ECHO(%%a) ELSE (SET "L1a=%%a"&CALL :sumlines)
)
FOR /f "skip=1delims=" %%a IN (%file1%) DO (ECHO(%%a)
) >%file3%
TYPE %file3%
GOTO :EOF
:sumlines
SET /a L2a=1%L1a:~10%+1%L2a:~10%
ECHO(%L1a:~0,10%%L2a:~1%
GOTO :eof
Note that if the value assigned to %filen% contains separators such as Space then %filen% must be quoted ("%%file3%").
If this results in the format
for /f ... in ("...") ...
then you need to add the usebackq option in for /f
eg.
FOR /f "USEBACKQdelims=" %%a IN ("%file2%") DO (
(caps used for emphasis; usebackq can be in any case)
If for /f ... is applied to "a string" without the usebackq option, then the "quoted string" is parsed, not the data in "the\file whose name \is in\the\quoted string"

How to replace segment delimiter or unwrap one-liner using Windows command line?

our system is going to be migrated from Linux to Windows machine so I'm preparing a batch file equivalent to our existing script. I already have created the batch file but I need to unwrap first the file before processing its next line of codes.
Example. Here is a one-liner wherein the delimiter is "{".
Note: Delimiter can be any or variable character except element delimiter ("~" in this case).
ISA~00~ ~00~ ~ZZ~SAMSUNGSND ~14~181087842 ~130214~2300~U~00401~000000003~0~T~>{GS~FA~181087842TEST~SYNNTEST~20130214~2300~810~X~004010{ST~997~131250001{AK1~SC~1809{AK9~A~1~1~1{SE~4~131250001{GE~1~810{IEA~1~000000001
I need it to be unwrapped like this (equivalent to tr "{" "\n" < FileName.txt ):
ISA~00~ ~00~ ~ZZ~SAMSUNGSND ~14~181087842 ~130214~2300~U~00401~000000003~0~T~>
GS~FA~181087842TEST~SYNNTEST~20130214~2300~810~X~004010
ST~997~131250001
AK1~SC~1809
AK9~A~1~1~1
SE~4~131250001
GE~1~810
IEA~1~000000001
EDIT:
Once unwrapped, I need to search fixed values of third field if equal to "1145837" under GS segment (2nd line) and replace it with "1119283" (which is equivalent to sed '/^GS/ s/1145837/1119283/').
Below is my batch file. I need the code to be inserted somewhere inside :WriteToLogFile subroutine
#echo on
::This ensures the parameters are resolved prior to the internal variable
SetLocal EnableDelayedExpansion
rem Get current date and time as local time.
for /f "delims=" %%a in ('wmic OS Get localdatetime ^| %SystemRoot%\System32\Find.exe "."') do set dt=%%a
rem Reformat the date and time strong to wanted format.
set "YYYY=%dt:~0,4%"
set "MM=%dt:~4,2%"
set "DD=%dt:~6,2%"
set "HH=%dt:~8,2%"
set "Min=%dt:~10,2%"
set "Sec=%dt:~12,2%"
set "TimeStamp=%YYYY%-%MM%-%DD%_%HH%-%Min%-%Sec%"
rem Define name of the list file containing current date and time in name.
set "ListFile=FLIST_%TimeStamp%.lst"
rem Change directory (and drive).
cd /D "C:\VP"
rem Create the list file which is good here as the list of files changes
rem while running this batch file and therefore it is better to work with
rem a list file instead of running a FOR directly for each file in the
rem directory. The list file is not included in this list as it either does
rem not exist at all or it has wrong file extension as only *.txt files are
rem listed by command DIR. The log file EDI.log has also wrong file extension.
dir *.txt /A:-D /B /O:D >"C:\VP\TEST\%ListFile%"
rem It might be useful to delete the log file from a previous run.
if exist EDI.log del EDI.log
rem Process each file in the list file.
cd /D "C:\VP\TEST"
for /F "delims=" %%F in ( %ListFile% ) do call :ProcessFile "%%F"
cd /D "C:\VP"
::rem Delete the list file as not needed anymore. It could be also kept.
::del %ListFile%
rem Exit batch file.
endlocal
goto :EOF
:ProcessFile
rem The parameter passed from first FOR is the file name in double quotes.
set "FileName=%~1"
rem Ignore the files CNtable.txt and Dupfile.txt in same directory.
rem Command goto :EOF just exits here from subroutine ProcessFile.
if "%FileName%"=="CNtable.txt" goto :EOF
if "%FileName%"=="Dupfile.txt" goto :EOF
if "%FileName%"=="VanPointAS2in.bat" goto :EOF
if "%FileName%"=="VP.bat" goto :EOF
rem Get 7th, 9th and 14th element from first line of current file.
cd /D "C:\VP"
for /f "usebackq tokens=7,9,14 delims=~*^" %%a in ( "%FileName%" ) do (
set "ISAsender=%%a"
set "ISAreceiver=%%b"
set "ISActrlnum=%%c"
goto WriteToLogFile
)
:WriteToLogFile
rem Remove all spaces as ISAsender and ISAreceiver have
rem usually spaces appended at end according to example
rem text. Then write file name and the 3 values to log file.
set "ISAsender=%ISAsender: =%"
set "ISAreceiver=%ISAreceiver: =%"
set "ISActrlnum=%ISActrlnum: =%"
echo %FileName%,%ISAsender%,%ISAreceiver%,%ISActrlnum%>>"C:\VP\TEST\EDI.log"
set "FLAG=N"
if "%ISAsender%"=="APPLESND" (
if "%ISAreceiver%"=="MANGO" (
set "FLAG=Y"
set "VW=AP"
call :DupCheck
echo %errorlevel%>>"C:\VP\TEST\EDI.log"
if errorlevel 1 move /Y "%FileName%" "APPLE"
echo Moved %FileName% to directory APPLE.
)
)
if "%ISAsender%"=="APPLESND" (
if "%ISAreceiver%"=="MANGOES" (
set "FLAG=Y"
set "VW=AP"
call :DupCheck
echo %errorlevel%>>"C:\VP\TEST\EDI.log"
if errorlevel 1 move /Y "%FileName%" "APPLE"
echo Moved %FileName% to directory APPLE.
)
)
if "%ISAsender%"=="SAMSUNGSND" (
if "%ISAreceiver%"=="MANGO" (
set "FLAG=Y"
set "VW=SS"
call :DupCheck
echo %errorlevel%>>"C:\VP\TEST\EDI.log"
if errorlevel 1 move /Y "%FileName%" "SAMSUNG"
echo Moved %FileName% to directory SAMSUNG.
)
)
rem Move to directory BYPASS if all else not satisfied.
if "%FLAG%"=="N" (
move /Y "%FileName%" "BYPASS"
echo Moved %FileName% to directory BYPASS
)
rem Exit the subroutine WriteToLogFile.
goto :EOF
:DupCheck
rem Check for ISA control number in file %VW%_table.txt.
%SystemRoot%\System32\Findstr.exe /X /M /C:%ISActrlnum% "C:\VP\TEST\%VW%_table.txt" >nul
if errorlevel 1 goto NewControl
rem This ISA control number is already present in file %VW%_table.txt.
echo Duplicate control %ISActrlnum% found in file %FileName%.
echo %ISActrlnum%,%FileName%>>"C:\VP\TEST\Dupfile.txt"
move /Y "%FileName%" "DUPLICATES"
echo Moved %FileName% to directory DUPLICATES.
rem Exit the subroutine DupCheck.
goto :EOF
:NewControl
echo %ISActrlnum%>>"C:\VP\TEST\%VW%_table.txt"
Any help is appreciated.
Manipulating text files with native batch commands is rather tricky, and quite slow. Most tasks can be done, but it requires quite a few advanced batch techniques to make the solution robust.
You will probably be most happy with GnuWin32 - a free collection of unix utilities for Windows. You could then manipulate file content with familiar tools.
Another good alternative (my favorite - no surprise since I wrote it) is to use REPL.BAT - a hybrid JScript/batch utility that performs a regex search/replace operation on stdin and writes the result to stdout. It is pure script that will run natively on any Windows machine from XP forward. Full documentation is embedded within the script.
I recommend replacing your line delimiter with \r\n rather than \n, as that is the Windows standard for newlines.
Assuming REPL.BAT is in your current directory, or somewhere within your PATH, then the following will make your needed changes:
set "file=fileName.txt"
type "fileName.txt" | repl "{" "\r\n" lx >"%file%.new"
move /y "%file%.new" "%file%" >nul
:GS_replace
<"%file%" call repl "^(GS~.*)1145837" "$11119283" >"%file%.new"
set "rtn=%errorlevel%"
move /y "%file%.new" "%file%" >nul
if %rtn% equ 0 goto GS_replace
I'm concerned that your string of numeric digits could be embedded within a larger number, leading to an unwanted substitution. You might want to refine your search term to prevent this.
The following would only replace an entire field:
:GS_replace
<"%file%" call repl "^(GS~(?:.*~)*)1145837(~|$)" "$11119283$2" >"%file%.new"
set "rtn=%errorlevel%"
move /y "%file%.new" "%file%" >nul
if %rtn% equ 0 goto GS_replace
The following would only replace an entire number that may be embedded within a larger alpha-numeric string:
:GS_replace
<"%file%" call repl "^(GS~(?:.*\D)*)1145837(\D|$)" "$11119283$2" >"%file%.new"
set "rtn=%errorlevel%"
move /y "%file%.new" "%file%" >nul
if %rtn% equ 0 goto GS_replace
In your comment below, you say you want to restrict the number change to the 3rd field of GS lines. (This is quite different than what you stated in your original question.) This is much simpler - no loop is required:
type "%file%" | repl "^(GS~(.*?~){2})1145837(~|$)" "$11119283$2" >"%file%.new"
move /y "%file%.new" "%file%" >nul

Batch File to list folders and allow user selection

I have searched and searched to find a solution to (what feels like) a unique problem. Many
answers here have been quite helpful and have gotten me a long way, but the last bit has me stumped.
My batch file needs to open an executable in a folder with a variable name
This folder may be in one of two directories
This is what I originally had and it works
#ECHO OFF
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
SET DIR1="%USERPROFILE%\AppData\Local\Application Workspace\Profiles"
SET DIR2=C:\Application\SRW\Profiles
IF NOT EXIST %DIR1% (
GOTO PROFILE_2
) ELSE (
GOTO PROFILE_1
)
:PROFILE_1
for /f "delims=" %%A in ('dir %DIR1% /ad /b') do (
set foldername=%%~nxA
)
SET DIR1=%DIR1:"=%
SET DIR=%DIR1%\%foldername%\app.exe
GOTO START_APP
:PROFILE_2
for /f "delims=" %%A in ('dir %DIR2% /ad /b') do (
set foldername=%%~nxA
)
SET DIR=%DIR2%\%foldername%\app.exe
GOTO START_APP
:START_APP
START "" /b "%DIR%"
:EOF
ENDLOCAL
EXIT
Then I was thrown a curveball when I discovered that some users may have multiple profiles in the variable profile folder and they need to be able to select which one to use for that given task. Now I have a variable profile folder and either one or multiple variable profiles within that folder.
I have found code to list the folder names and display them in the command window for selection.
#echo off
cls
setlocal EnableDelayedExpansion
set /a count=0
for /d %%d in (*) do (
set /a count+=1
#echo !count!. %%d
)
setlocal DisableDelayedExpansion
set /P selection="select folder number:"
I also found this routine which allows the user to select a file from a list then it is supposed to translate that file name into a variable.
Batch Script Programming -- How to allow a user to select a file by number from a list of files in a folder?
Unfortunately, I cannot get the example in the link to work as is and I have no idea how to make it work with folder names as the folder name example and the file name example are close but not close enough for me to understand what to do. And even if it somehow does manage to work, how then can I make such a routine work within the original code posted above?
In addition, I really don't want the user to be forced to make a folder selection if there is only one folder. If only one folder exists, it should be placed into the folder name variable automatically and nothing displays to the user at all.
Is what I'm trying to do even possible at all?
Any help is most appreciated.
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
::
:: Set count-of-targets and application name
::
SET /a targets=0
SET appname=app.exe
SET "dots=..."
::
:: clear all "$" variables
::
FOR /f "delims==" %%i IN ('set $ 2^>nul') DO SET "%%i="
::
:: look for app.exe in (1) userprofile... (2) \application\srw...
::
SET dir1="%USERPROFILE%\AppData\Local\Application Workspace\Profiles"
SET dir2="C:\Application\SRW\Profiles"
:: My testing - my directory names
SET dir1="c:\sourcedir"
SET dir2="C:\destdir\test dir"
FOR %%s IN (%dir1% %dir2%) DO (
FOR /f "delims=" %%i IN ('dir /s /b "%%~s\%appname%"') DO ( CALL :process "%%~dpi"
)
)
::
:: Now have TARGETS=#hits; $_n=full pathname; $n=directoryname
::
IF %targets%==1 SET mychoice=1&GOTO chosen
::
:: repeat a menu
::
:again
CLS
FOR /l %%i IN (1,1,%targets%) DO (
IF %%i lss 10 (ECHO(%%i%dots%%dots:~0,1%!$%%i!) ELSE (ECHO(%%i%dots%!$%%i!)
)
SET mychoice=
SET /p mychoice="Please choose 1..%targets% : "
IF NOT DEFINED $_%mychoice% GOTO again
:chosen
CALL SET application="%%$_%mychoice%%%%appname%"
ECHO START "" /b %application%
GOTO :EOF
::
:: Parameter is quoted-full-pathname
::
:process
SET /a targets+=1
SET $_%targets%=%~1
SET $=%~1
FOR %%n IN ("%$:~0,-1%") DO SET $%targets%=%%~nxn
GOTO :eof
From what you've said, this should work.
First steps are to set up. Set the number of targets found and the real application name and clear out any variablenames that start "$".
Next step is to specify the directories to use. I simply copied yours, then overrode those settings with settings to suit my machine - you'd need to delete those overriding lines, of course. Note that the names should be set quoted...
Next, scan from each of the directories for the application. Each time an instance is found, call :process passing the full -terminated pathname.
:process increments the count of targets found and sets the variable $_n to the full pathname passed (dequoted) It then sets $ to the dequoted-full-pathname and uses a syntax-trick to have FOR find the application's immediate directory. %$:~0,-1% is the full pathname minus the last character - the \ so the results looks like a filename. That "filename" is then assigned to $n
Once the directory-scans are finished, %targets% will contain the er, number of targets. If that's 1, then we have all we need - we can only select target number 1, so that's chosen for us.
Otherwise, clear the screen and display a number of lines - %targets% to be precise. The format isn...immediatedirname` and an extra dot is added if the number of targets is less than 10 so that a long list will line up nicely.
Then ask for a line number.
At this point, the only $_ variables that exist are $_1..$_%targets% so if $_%mychoice% exists, it must be a valid choice. If it's not defined, then repeat the question...
CALLing SET application="%%$_%mychoice%%%%appname%" first parses the command, then parses it again. After the first parse, the line becomes (if mychoice=3) SET application="%$_3%app.exe" so on the second occasion,application` is properly set to the full fulename of the application - and quoted.
There wasn't much point in my starting the app, since it doesn't exist, so I just echoed it.
Given the extended requirement:
You's probably be starting this from a shortcut. Suppose you run that shortcut minimised and insert after the SETLOCAL
SET "poweruser=%1"
Add, after
IF %targets%==1 SET mychoice=1&GOTO chosen
the line
IF NOT DEFINED poweruser START "%username% - poweruser" "%~dpnx0" poweruser&GOTO :EOF
And change the
GOTO :EOF
just prior to the label :process to
EXIT
So that if it wasn't run in poweruser mode (therefore poweruser is NOT defined) AND %targets% is NOT 1 (I'm presuming it won't be 0) Then this user hasn't been set as a poweruser in the shortcut, but does have more than one target, so the job is restarted in poweruser mode (ie. maximised.)
Note that it doesn't actually matter what the string is after the "~dpnx0" - so long as it's a string. I just used poweruser What it does is tell the batch that it's being run in a normal window, not minimised.
For powerusrs, you could if you like set the shortcut to normal window or maximised AND put a parameter in the command line after the batchname, which will marginally quicken the procedure. Leaving it without parameters and minimised would suit ALL users as it will auto-adjust if more than 1 target is found.
I have tried this. It worked for me.
I answered your question in two steps. In the first one I translated your original code into a more readable one. Here it is:
#ECHO OFF
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
SET "DIR1=%USERPROFILE%\AppData\Local\Application Workspace\Profiles"
SET DIR2=C:\Application\SRW\Profiles
IF NOT EXIST "%DIR1%" (
for /f "delims=" %%A in ('dir %DIR2% /ad /b') do (
SET DIR=%DIR2%\%%~nxA\app.exe
)
) ELSE (
for /f "delims=" %%A in ('dir %DIR1% /ad /b') do (
SET DIR=%DIR1%\%%~nxA\app.exe
)
)
START "" /b "%DIR%"
ENDLOCAL
EXIT
You should check first that previous code is equivalent to your original one.
In the second step I added the profile selection to the previous code:
#ECHO OFF
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
SET "DIR1=%USERPROFILE%\AppData\Local\Application Workspace\Profiles"
SET DIR2=C:\Application\SRW\Profiles
IF NOT EXIST "%DIR1%" (
for /f "delims=" %%A in ('dir %DIR2% /ad /b') do (
SET DIR=%DIR2%\%%~nxA\app.exe
)
) ELSE (
call :SelectProfile
)
START "" /b "%DIR%"
ENDLOCAL
EXIT
:SelectProfile
rem Get a list of folders in User Profile dir
set count=0
for /f "delims=" %%A in ('dir %DIR1% /ad /b') do (
set /A count+=1
SET DIR[!count!]=%DIR1%\%%~nxA
)
rem Initialize the selected profile
set select=1
if %count% equ 1 goto endSelection
rem Show the profiles menu
cls
echo Available profiles:
echo/
for /L %%i in (1,1,%count%) do echo %%i- !DIR[%%i]!
echo/
rem Let's the user to select the profile
:select
set select=1
set /P "select=Enter number of desired profile [first one]: "
if not defined DIR[!select!] goto select
:endSelection
SET DIR=!DIR[%select%]!\app.exe
exit /B
Please note that previous code fail if the user enter spaces in the answer. This detail may be fixed, if needed.

Resources