How to put batch code in one line command - cmd

I'm newbie with command line and batch files.
I have a task to make a code which checks the txt-file for presence of specific line. If the line doesn't exist in the file the code is supposed to add this specific line. The problem is that I have to make this code in one line because it will be used in software which GUI can keep and execute only one command line but not batch file.
Here what I have:
File 1.txt with lines:
ab cde
1 23
456
My command line:
set /a f=3 & set "My_str=f gh" & (for /f "delims=" %x in (1.txt) do if "%My_str%"=="%x" set /a f=1 ) & ( if !f! lss 2 call echo %My_str%>>1.txt)
So I use flag f to indicate the existence of line f gh in my txt-file. Flag value f=3 means the line doesn't exist and it needs to be added. If the line exist the flag will have value f=1 and condition (!f! lss 2) will not trigger adding the line.
It looks like if !f! lss 2 doesn't work how I want and my command just add every time the line f gh although it already exist in the file (e.g. after previous executions in other cmd windows before).
Thanks.

If your command is run with delayed expansion ON (cmd /V:ON), it seems that your !f! flag check sould be then IF NOT !f! lss 2 call echo ... or, as !f! here is a flag, IF NOT !f! == 1 call echo .... The if !f! lss 2 call .. means "if !f! is lower than 2" and these will trigger the add if !f! is 1. I also guess you may need to activate delayed expansion.
You should then force it with :
cmd /V:ON /C "set /a f=3 & set "My_str=f gh" & (for /f "delims=" %x in (1.txt) do if "%My_str%"=="%x" set /a f=1 ) & ( if NOT !f! lss 2 call echo %My_str%>>1.txt)"

Why so complicated? Why not using a variable that simply indicates presence of the line string just by being defined or not?
set "F=" & (for /F "delims=" %X in (1.txt) do if "%X"=="f gh" set "F=#") & if not defined F >>1.txt echo/f gh
Or not using for /F but findstr instead:
> nul findstr /X /C:"f gh" 1.txt || >>1.txt echo/f gh

Related

Extract a section from the filename of a Batch variable

File: Test 123 - Test 456 - Test 789.txt
I need to extract the first section from a passed parameter inside a Batch file. In this case it would be "Test 123" but the files always have different names. " -" needs to be the delimiter (space + hyphen).
%~n1 expands %1 to file name only but how to specify only one section of the filename?
Edit: Thanks for all the help but only the PowerShell solution from LotPings works as expected! The others echoed an empty filename. I don't know why but I'm sure it has something to do with my setup.
…And another:
#Echo Off
Set "filename=%~n1"
Set "newname=%filename: -="&:"%"
Echo "%newname%%~x1"
Pause
GoTo :EOF
Another solution using PowerShell
#Echo off
For /f "delims=" %%A in ('
Powershell -NoP -C "('%~1' -Split ' - ')[0]"
') Do Set "NewName=%%A%~x1"
Set NewNAme
> SO_50887843_2.cmd " -;Test 123 - Test 456 =! Test 789.txt"
NewName= -;Test 123.txt
With string substitution you can do a bit shuffling (with unquoted arguments)
:: SO_50887843.cmd
#Echo off
Set "_Args=%*"
:: remove content up to first delimiter " - "
Set "_Rest=%_Args:* - =%"
:: remove " - " and Rest from Args
Call Set "_First=%%_Args: - %_Rest%=%%"
Set _
> SO_50887843.cmd Test 123 - Test 456 - Test 789.txt
_Args=Test 123 - Test 456 - Test 789.txt
_First=Test 123
_Rest=Test 456 - Test 789.txt
With quoted args change 2nd line to:
Set "_Args=%~1"
This commented code can be used for this task:
#echo off
if "%~1" == "" goto :EOF
setlocal EnableExtensions DisableDelayedExpansion
rem Get file name without extension and path assigned to an environment variable.
set "FileName=%~n1"
rem For file names starting with a dot and not having one more dot like .htaccess.
if not defined FileName set "FileName=%~x1"
rem Exit the batch file if passed argument is a folder path ending with a backslash.
if not defined FileName goto EndBatch
rem Replace each occurrence of space+hyphen+space and next also of just
rem space+hyphen by a vertical bar in file name. A vertical bar is used
rem because a file name cannot contain this character.
set "FileName=%FileName: - =|%"
set "FileName=%FileName: -=|%"
rem Get first vertical bar delimited string assigned to the environment variable.
for /F "eol=< delims=|" %%I in ("%FileName%") do set "FileName=%%I"
echo First part of "%~nx1" is "%FileName%".
rem Add here more commands using the environment variable FileName.
:EndBatch
endlocal
This batch file must be called with the file name enclosed in double quotes because of file name contains paces, for example:
GetFirstFileNamePart.bat "Test 123 - Test 456 - Test 789.txt"
This batch file works even on calling it with following very strange file name:
GetFirstFileNamePart.bat " - Test 123 -Test 456 != Test 789 & More.txt"
The output is in this case:
First part of " - Test 123 -Test 456 != Test 789 & More.txt" is "Test 123".
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 /?
rem /?
set /?
setlocal /?

Why does my batch file fail to get file size?

I'm trying to write a Windows batch file that uses ffmpeg to convert whole folders with old *.flv videos into *.mp4 videos.
The batch file more or less works, but I want to do some test before deleting the source file. One of these test is that the output file should be at least 2/3 of the original file, but I can't get it to work.
Here's my bat file (with all the debugging echo lines included):
#echo off
setlocal EnableExtensions EnableDelayedExpansion
::-------------------------------------------------------------------------------------
:: get options and folder path
set opzione=%~1%
set cartella=%~2%
:: who's who?
if "%opzione:~3,1%"=="" (
echo.
) else (
if "%opzione:~0,1%"=="/" (
echo.
) else (
set opzione=%~2%
set cartella=%~1%
)
)
::echo.
::echo Cartella = %cartella%
::echo Opzione = %opzione%
::echo.
::-------------------------------------------------------------------------------------
:Check_path
set FLV_FOLDER="%cartella%"
if %FLV_FOLDER% == "" (
echo ... Invalid
goto :uscita
) else (
echo ... OK.
)
::-------------------------------------------------------------------------------------
:Check_Options (STILL W.I.P.)
set Lista=0
set Convert=0
set Delete=0
if "%opzione%"=="/c" (set Convert=1)
if "%opzione%"=="/l" (set Lista=1)
if "%opzione%"=="/d" (set Delete=1)
::echo Lista = %Lista%
::-------------------------------------------------------------------------------------
:Loop_path
#cls
echo Looping all .flv files in %FLV_FOLDER%...
for /R %FLV_FOLDER% %%a IN (*.flv) do call :Converting_Function "%%a"
goto :uscita
::-------------------------------------------------------------------------------------
:Converting_Function
set infile="%~1"
set outfile="%~dpn1.mp4"
set outsize=0
set insize=0
set minsize=0
if not %Lista%==0 goto :just_list
echo Converting %infile% to %outfile%
ffmpeg -v error -i %infile% -c copy -copyts %outfile%
::....................CHECKS........................................................
echo Errors from ffmpeg?
if errorlevel 1 goto :error_ffmpeg
echo Do the outfile exist?
if not exist %outfile% goto :error_exist
echo Is outfile big enough?
:: (say yes if outfile size > infile size*2/3)
for /f %%S in (%outfile%) do set "outsize=%%~zS"
echo %outfile% size is %outsize%
for /f %%S in (%infile%) do set insize=%%~zS
echo %infile% size is %insize%
set /A "minsize=(%insize%*3)/2"
echo minsize is %minsize%
if not %outsize% GTR %minsize% goto :error_size
ren "%~1" "%~n1.todelete"
:: del /q %infile%
goto :eof
:error_ffmpeg
echo Convertion error
pause
if exist %outfile% del /q %outfile%
goto :eof
:error_exist
echo %outfile% does not exist
pause
goto :eof
:error_size
echo Size of %outfile% is 0
pause
goto :eof
:just_list
echo %infile%
goto :eof
:uscita
pause
This is the output:
Converting "T:\C++Stuffs\_PROVACONV_\Monaco - Machine_#1 - Monday Morning.flv" to "T:\C++Stuffs\_PROVACONV_\Monaco - Machine_#1 - Monday Morning.mp4"
[flv # 0000000000577320] Packet mismatch 107347968 1638 1638
Errors from ffmpeg?
Do the outfile exist?
Is outfile big enough?
"T:\C++Stuffs\_PROVACONV_\Monaco - Machine_#1 - Monday Morning.mp4" size is
"T:\C++Stuffs\_PROVACONV_\Monaco - Machine_#1 - Monday Morning.flv" size is
Operando mancante.
minsize is 0
0 non atteso.
D:\ffmpeg-20170204-b1e2192-win64-static\bin>
Operando mancante means Missing Operand, 0 non atteso means Unexpected 0
Why do I not have the file size in the variables? What is the missing operand?
The environment variables infile and outfile are defined with file name being enclosed in double quotes. That is not recommended as explained in answer on Why is no string output with 'echo %var%' after using 'set var = text' on command line? But it is valid and works here as expected.
The command line to get file size of output file
for /f %%S in (%outfile%) do set "outsize=%%~zS"
is processed before execution by Windows command interpreter for example to
for /f %S in ("C:\Path\File Name.mp4") do set "outsize=%~zS"
It can be read on executing in a command prompt window for /? that for /F interprets the set (string between round brackets) as string to process if enclosed in double quotes except the option usebackq is used which is not done here. For that reason FOR splits up the string C:\Path\File Name.mp4 into tokens using space/tab as delimiters and assigns the first token to loop variable S. So assigned to S for the example is C:\Path\File. The file size for this file can't be determined by Windows command interpreter as this file does not exist.
The solution is using FOR without option /F:
for %%S in (%outfile%) do set "outsize=%%~zS"
And the command line to get file size of input file
for /f %%S in (%infile%) do set insize=%%~zS
can be replaced by
set "insize=%~z1"
The help output on running in a command prompt window call /? explains this argument modifier for getting the size of a file passed as first argument to the batch file on calling it.
The help output on running set /? explains that in arithmetic expressions the current values of environment variables can be referenced by specifying the environment variables with just their names without using % or !. This works even within a command block beginning with ( and ending with matching ).
The command line with the arithmetic expression
set /A "minsize=(%insize%*3)/2"
can result on insize not being defined in execution of
set /A "minsize=(*3)/2"
This explains the error message because there is indeed missing the left operand for the multiplication.
The solution is using the arithmetic expression as recommended by help of command SET.
set /A "minsize=(insize*3)/2"
This arithmetic expression never fails on evaluation. In case of environment variable insize is not defined, it is replaced by 0 on evaluation of the arithmetic expression as explained by the help.
See also Debugging a batch file.
And please note that Windows command interpreter supports only arithmetic expressions with 32-bit signed integer values. So video files with a file size of 2 GiB or more cannot be correct processed by your batch code.

CMD skips a line in batch file for no apparent reason, so what's the reason?

Simply script, probably a simple question:
set /p customsettings="Some input prompt: "
if /i %customsettings:~0,1% equ Y echo Some output
^-This works fine...
set custom=1
if %custom% equ 1 (
set /p customsettings="Some input prompt: "
echo Some output
)
^-...and this works fine.
So why doesn't this work fine?:
set custom=1
if %custom% equ 1 (
set /p customsettings="Some input prompt: "
if /i %customsettings:~0,1% equ Y echo Some output
)
The set /p customsettings line get skipped only when it is pinched between two if-statements.
I'm curious why this happens, and how to fix it.
Note: The problem still persists regardless of EnableDelayedExpansion's setting.
You can read a lot about delayed expansion on this site. An entire IF/FOR construct (or multiple lines within parens) are loaded and expanded as 1 line. So you have to consider LOAD-TIME behavior and RUN-TIME behavior. Try this:
#echo off
setlocal enabledelayedexpansion
set custom=1
if %custom% equ 1 (
set /p customsettings="Some input prompt: "
if /i "!customsettings:~0,1!"=="Y" echo Some output
)

cmd: variable of open with

When, for example, i want a batch file to 'open' a file. when i for example drag and drop the file into the batch file, it should do some stuff with that file.
Now, i need to know the variable. I know there is a variable for this kind of stuff; i just forgot it.
Can someone give me the variable please?
Thanks.
The first 9 parameters given to a batch file can be accessed by writing %1 through %9.
The complete command line argument is stored in %*.
For more information, see here.
Drag&Drop to a batch file can be a much more difficult job.
Because windows doesn't know how to add the files in the correct way.
If your files are simple, it works as expected.
1.txt
2 3.txt
4 & 5.txt
drag.bat 1.txt "2 3.txt" "4 & 5.txt"
But some filenames are confusing windows...
6,7.txt
8&9.txt
drag.bat 6,7.txt 8&9.txt
-- results in --
%1 = 6
%2 = 7.txt
%3 = 8
%4 =
The command "9.txt" can not be found
In the first moment it seems an impossible problem,
but it exists a solution.
The trick is to use the cmdcmdline variable instead of the parameters %1..%9
The cmdcmdline contains something like
cmd /c ""C:\dragTest\test.bat" C:\dragTest\1.txt "C:\dragTest\2 3.txt"
C:\dragTest\6,7.txt C:\dragTest\8&9.txt"
So you can work with this, but you have to stop your batch after all, so the 9.txt can't be executed.
#echo off
setlocal ENABLEDELAYEDEXPANSION
rem Take the cmd-line, remove all until the first parameter
set "params=!cmdcmdline:~0,-1!"
set "params=!params:*" =!"
set count=0
rem Split the parameters on spaces but respect the quotes
for %%G IN (!params!) do (
set /a count+=1
set "item_!count!=%%~G"
rem echo !count! %%~G
rem Or you can access the parameter with, but this isn't secure with special characters like ampersand
rem call echo %%item_!count!%%
)
rem list the parameters
for /L %%n in (1,1,!count!) DO (
echo %%n #!item_%%n!#
)
pause
REM *** EXIT *** is neccessary to prevent execution of "appended" commands
exit

batch parameters: everything after %1

Duplicate:
Is there a way to indicate the last n parameters in a batch file?
how to get batch file parameters from Nth position on?
Clarification: I knew of the looping approach - this worked even before Command Extensions; I was hoping for something fun and undocumented like %~*1 or whatever - just like those documented at http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/percent.mspx?mfr=true.
In a Windows batch file (with the so called "Command Extensions" on), %1 is the first argument, %2 is the second, etc. %* is all arguments concatenated.
My question: is there a way to get everything AFTER %2, for example?
I couldn't find such a thing, and it would be helpful for something I'm working on.
There is a shorter solution (one-liner) utilizing the tokenization capabilities of for loops:
:: all_but_first.bat
echo all: %*
for /f "tokens=1,* delims= " %%a in ("%*") do set ALL_BUT_FIRST=%%b
echo all but first: %ALL_BUT_FIRST%
output:
> all_but_first.bat foo bar baz
all: foo bar baz
all but first: bar baz
Footnote: Yes, this solution has issues. Same as pretty much anything written with batch files. It's 2021. Use Powershell or literally any other actual scripting language.
I am not sure if there is a direct command but you can always use a simple loop and shift to get the result in a variable. Something like:
#echo off
set RESTVAR=
shift
:loop1
if "%1"=="" goto after_loop
set RESTVAR=%RESTVAR% %1
shift
goto loop1
:after_loop
echo %RESTVAR%
Let me know if it helps!
The following will work for args with ", =, ' '. Based on Dmitry Sokolov answer. Fixed issue when second arg is the same as first arg.
#echo off
echo %*
set _tail=%*
call set _tail=%%_tail:*%1=%%
echo %_tail%
The following will work for args with ", =, ' ' (as compared to #MaxTruxa answer)
echo %*
set _all=%*
call set _tail=%%_all:*%2=%%
set _tail=%2%_tail%
echo %_tail%
Test
> get_tail.cmd "first 1" --flag="other options" --verbose
"first 1" --flag="other options" --verbose
--flag="other options" --verbose
You can use SHIFT for this. It removes %1 and shifts all other arguments one lower. This script outputs all the arguments after %2 (so it outputs %3, %4...) until one of them is empty (so it's the last one):
#echo off
SHIFT
SHIFT
:loop
if "%1" == "" goto end
echo %1
SHIFT
goto loop
:end
EDIT: Removed example using %* as this doesn't work - %* always outputs all of the parameters
Building on schnaader's answer, I think this does it if you want everything after %1 concatenated.
#echo off
SHIFT
set after1=
:loop
if "%1" == "" goto end
set after1=%after1% %1
SHIFT
goto loop
:end
echo %after1%
Sebi, here's the Syntax!
There is a behavior, batch eating the equal signs which is not double quoted, it cause trouble in the scripts above. If you wan't to skip, i've made a modification, based on Raman Zhylich answer and strlen.cmd:
#ECHO OFF
SETLOCAL enableDelayedExpansion
SET _tail=%*
SET "_input="
SET /A _len=0
:again
SET "_param=%1"
SET "_input=%_input%%1"
FOR /L %%i in (0,1,8191) DO IF "!_param:~%%i,1!"=="" (
REM skip param
SET /A _len+=%%i
REM _len can't be use in substring
FOR /L %%j in (!_len!,1,!_len!) DO (
REM skip param separator
SET /A _len+=1
IF "!_tail:~%%j,1!"=="=" (SET "_input=%_input%=" & SHIFT & goto :again)
)
) & goto :next
:next
IF %_len% NEQ 0 SET _tail=!_tail:~%_len%!
ENDLOCAL & SET "_input=%_input%" & SET "_tail=%_tail%"

Resources