I have a file with contents like this:
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>8.10</string>
<key>CFBundleURLTypes</key>
To extract the line with CFBundleShortVersionString, I can use:
(for /f "delims=," %a in ('type "Info.pList" ^|findstr "CFBundleShortVersionString"') do #echo %a) > "CFBundleShortVersionString.txt"
gives output
<key>CFBundleShortVersionString</key>
But how do I extract the line below that please? i.e.
<string>8.10</string>
I am using the command prompt in Windows 10.
Thanks,
Chris.
If you only need those two lines let's just use this trick
You can paste this into the CLI:
SET "_Found="
#for /f "delims=," %a in ('
Type "Info.pList"
') do #(
IF NOT DEFINED _Found (
ECHO=%a|find "CFBundleShortVersionString" && (
SET "_Found=1"
)
) ELSE (
ECHO=%a
SET "_Found="
)
)>> "CFBundleShortVersionString.txt"
This will output every match and the first line after each match
You could first determine the number of the line containing the search string, then read the file and skip that number of lines, like shown in the following batch-file:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Initialise variables:
set "LineNumber=" & set "LineString="
rem // Let `findstr` precede the matching line(s) with line number plus a colon:
for /F "delims=:" %%L in ('findstr /N "CFBundleShortVersionString" "Info.pList"') do (
rem // Fetch the line number (remove `if defined LineNumber` if you want the last match):
if not defined LineNumber set /A "LineNumber=%%L"
)
rem // Check whether the search string has been found:
if defined LineNumber (
rem // Read the file but skip lines up to the one containing the search string:
for /F "usebackq skip=%LineNumber% delims=" %%S in ("Info.pList") do (
rem // This captures the next (non-empty) line following the one containing the search string:
if not defined LineString set "LineString=%%S"
)
)
rem // Return result:
echo This is the extracted line: "%LineString%"
endlocal
exit /B
Here is one way to use a regex to get the next line. Select-String is not sed or awk, but it can do some things.
FOR /F "delims=" %%A IN ('powershell -NoLogo -NoProfile -Command ^
"(Get-Content -Path C:\src\t\Get-NextLine.txt -Raw |" ^
"Select-String -Pattern 'CFBundleShortVersionString.*\n(.*)\n').Matches.Groups[1].Value"') DO (
SET "NEXT_LINE=%%~A"
)
ECHO NEXT_LINE is "%NEXT_LINE%"
If the goal is to get 8.10 from the line, a little more work on the -Pattern could do that.
NB: If ECHO %NEXT_LINE% is used, the redirection characters <> will be interpreted. That is why the last output line uses QUOTATION MARK characters.
Related
I know batch isn't the best vehicle for this but my requirements dictate that I keep it.
I have text that looks like the following (it also has blank lines):
Line AAA text
Line BBB text
! ***### START
Body text here
! ***### END
Line XXX
Line YYY
!Comment Line etc
I want to remove the ! ***### START and END lines and everything in between and then save over the original file.
I found and modified the code below but it strips out my blank lines and the ! characters.
#echo off
setlocal enabledelayedexpansion
set "sourceFile=c:\temp\startfile.txt"
set "tempFile=c:\temp\tempfile.txt"
set "StartPhrase=! ***### START"
set "EndPhrase=! ***### END"
set /a lineNum=0
REM check file for search phrase, store line as refLine
FOR /F "delims=" %%i IN (%sourceFile%) DO (
set /a lineNum+=1
echo !lineNum! = "%%i"
if "%%i" == "%StartPhrase%" (
echo Found "%StartPhrase%" on line !lineNum!
set /a StartrefLine=!lineNum!
)
if "%%i" == "%EndPhrase%" (
echo Found "%EndPhrase%" on line !lineNum!
set /a EndrefLine=!lineNum!
)
)
REM make backup
copy "%sourceFile%" "%sourceFile%-%DATE:/=-% %TIME::=-%.txt"
echo. 2>%tempFile%
REM Rewrite file
set /a lineNum=0
set /a lowEnd=%StartrefLine%
echo "Set low end to %lowEnd%"
set /a highEnd=%EndrefLine%
echo "Set high end to %highEnd%"
FOR /F "delims=" %%i IN (%sourceFile%) DO (
set /a lineNum+=1
if !lineNum! GTR %lowEnd% (
if !lineNum! LSS %highEnd% (
echo "Skipping line #!lineNum!"
)
)
if !lineNum! LSS %lowEnd% (
echo "Writing Line !lineNum! %%i to temp file..."
echo %%i >> %tempFile%
)
if !lineNum! GTR %highEnd% (
echo "Writing Line !lineNum! %%i to temp file..."
echo %%i >> %tempFile%
)
)
REM get target filename only
for %%F in ("%sourceFile%") do set fname=%%~nxF
REM del original file and rename tempfile
echo "Deleting original file..."
echo Y | del "%sourceFile%"
echo "Renaming %tempFile% to %fname%"
ren "%tempFile%" "%fname%"
A possible and quite simple way is to let the particular line markers toggle a flag that indicates whether or not the currently iterated line is to be output:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_FILE=C:\TEMP\startfile.txt"
set "_TMPF=%TEMP%\%~n0_%RANDOM%.tmp"
set "_START=! ***### START"
set "_END=! ***### END"
rem // Initialise flag:
set "FLAG=#"
rem // Write to temporary file:
> "%_TMPF%" (
rem /* Loop through lines of input file; `findstr` precedes each line with
rem line number plus `:`, so they do not appear empty to `for /F`: */
for /F "delims=" %%L in ('findstr /N "^" "%_FILE%"') do (
rem // Store current line string (with line number prefix) to variable:
set "LINE=%%L"
rem // Toggle delayed expansion to avoid loss of `!`:
setlocal EnableDelayedExpansion
rem // Remove line number prefix to retrieve original line string:
set "LINE=!LINE:*:=!"
rem // Check contents of current line:
if "!LINE!"=="!_START!" (
endlocal & set "FLAG="
) else if "!LINE!"=="!_END!" (
endlocal & set "FLAG=#"
) else (
rem // Check state of flag for outputting:
if defined FLAG echo(!LINE!
endlocal
)
)
) && (
rem // Move temporary file onto target file:
move /Y "%_TMPF%" "%_FILE%"
)
endlocal
exit /B
This task could be done with worst script interpreter available for this task with following code:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "SourceFile=C:\Temp\startfile.txt"
if not exist "%SourceFile%" goto :EOF
set "TempFile=%SourceFile%.tmp"
set "PrintLines=1"
(for /F "delims=" %%I in ('%SystemRoot%\System32\findstr.exe /N /R "^" "%SourceFile%"') do (
set "Line=%%I"
setlocal EnableDelayedExpansion
if defined PrintLines (
if not "!Line: ***### START=!" == "!Line!" (
endlocal
set "PrintLines="
) else (
echo(!Line:*:=!
endlocal
)
) else (
if not "!Line: ***### END=!" == "!Line!" (
endlocal
set "PrintLines=1"
) else endlocal
)
))>"%TempFile%"
move /Y "%TempFile%" "%SourceFile%"
if exist "%TempFile%" del "%TempFile%"
endlocal
for /F results in ignoring all empty lines. For that reason command findstr is used to output all lines with line number and colon at beginning in a separate command process started with %ComSpec% /c in background. So there is no empty line anymore in captured output of findstr.
Each captured line starting with a digit in range 1 to 9 is assigned completely to the loop variable I because of using option delims= to define an empty list of string delimiters to disable the default line splitting behavior on normal spaces and horizontal tabs.
The line with line number and colon at beginning is assigned to the environment variable Line while delayed environment variable expansion is disabled. This is important as otherwise the Windows command processor cmd.exe processing the batch file would parse the command line set "Line=%%I" after having replaced %%I by current line with enabled delayed expansion and would interpret each ! as beginning/end of a delayed expanded environment variable.
Then the delayed expansion is enabled for further processing the line depending on printing lines currently enabled as by default until a line is found containing the string ***### START. Next the environment variable PrintLines is undefined until a line is found containing the string ***### END on which PrintLines is defined again for the following lines.
Please read second half of this answer for details about the commands SETLOCAL and ENDLOCAL.
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.
del /?
echo /?
endlocal /?
findstr /?
for /?
if /?
move /?
set /?
setlocal /?
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
SET "destdir=U:\destdir"
SET "filename1=%sourcedir%\q65218525.txt"
SET "outfile=%destdir%\outfile.txt"
SET "blockstart=! ***### START"
SET "blockend=! ***### END"
SET "repro=Y"
(
FOR /f "tokens=1*delims=:" %%a IN ('findstr /n /r ".*" "%filename1%"') DO (
IF /i "%%b"=="%blockstart%" SET "repro="
IF /i "%%b"=="%blockend%" (SET "repro=Y") ELSE (IF DEFINED repro IF "%%b"=="" (ECHO.) ELSE (ECHO %%b))
)
)>"%outfile%"
GOTO :EOF
You would need to change the settings of sourcedir and destdir to suit your circumstances. The listing uses a setting that suits my system.
I used a file named q65218525.txt containing your data for my testing.
Produces the file defined as %outfile%
Since your code saves the original file under a new name and creates a new file with the old name but your requirement seems to be to simply overwrite the old file, I'll leave that part as an exercise.
First, define the strings involved, and a flag (which I've called repro) which is initialised to reproduce the data.
Then use findstr with the /N option to prefix each line, including blank lines, with number: which is then parsed by the for/r to put the number in %%a and the text in %%b.
Check %%b for the target strings and frob the repro flag as appropriate to select whether to reproduce the line read or not.
Another way to do this would be using PowerShell. Invoke it from the command line or a .bat file script using:
powershell -NoLogo -NoProfile -File '.\Slice-File.ps1'
=== Slice-File.ps1
$SourceFile = 'C:\src\t\Slice-File.txt'
$TempFile = New-TemporaryFile
$WriteIt = $true
Get-Content -Path '.\Slice-File.txt' |
ForEach-Object {
if ($_ -eq '! ***### START') { $WriteIt = $false }
if ($WriteIt) { $_ }
if ($_ -eq '! ***### END') { $WriteIt = $true }
} |
Out-File -FilePath $TempFile -Encoding ascii
Remove-Item -Path $SourceFile
Move-Item -Path $TempFile -Destination $SourceFile
What I need is the first sequence of a number in a listing:
The command:
for /f "delims=" %%a in (notas.txt) do #echo %%a
Returns:
Compra cfme NF 12345 de 123 CIA ABC
Pgto dupl. 12345 - 123 CIA ABC
Compra cfme NFS 654321-CIA CBC
Pgto NF 654321 de CIA CBC
But what I need is:
12345
12345
654321
654321
Thanks in advance
There are numerous PowerShell solution/techniques that can perform what is required.
The switch statement can be used with the -File and -Regex parameters.
switch -Regex -File notas.txt {
'\d+' { $Matches[0] }
}
You can also use the Match method from the regex class:
Get-Content notas.txt | Foreach-Object {
[regex]::Match($_,'\d+').Value
}
Both solutions rely on regex matching with \d+ matching one or more consecutive digits. Since we are doing a single match per line, the first match is the only match returned. The regex class method Matches returns multiple matches per string input.
With a Batch file using regex in vbscript you can do something like this :
Demo Here
#echo off & color 0A
Title Extract Numbers from String using Regex with vbscript
Set "InputFile=%~dp0notas.txt"
Set "OutPutFile=%~n0_OutPutFile.txt"
Call :Extract_Number "%InputFile%" CON
Call :Extract_Number "%InputFile%" "%OutPutFile%"
TimeOut /T 3 /NoBreak>nul
If Exist "%OutPutFile%" Start "" "%OutPutFile%" & Exit
::-----------------------------------------------------------------------------------
:Extract_Number <Input> <OutPut>
(
echo WScript.StdOut.WriteLine Extract_Number(Data^)
echo Function Extract_Number(Data^)
echo Data = "%~1"
echo Data = WScript.StdIn.ReadAll
echo Set re = New RegExp
echo re.Global = True
echo re.IgnoreCase = True
echo re.Pattern = "\d{5,}"
echo For Each Match in re.Execute(Data^)
echo Number = Number ^& Match.Value ^& vbCrLF
echo Next
echo Extract_Number = Number
echo End Function
)>"%tmp%\%~n0.vbs"
cscript //nologo "%tmp%\%~n0.vbs" < "%~1" > "%~2"
If Exist "%tmp%\%~n0.vbs" Del "%tmp%\%~n0.vbs"
Exit /B
::----------------------------------------------------------------------------------
This first code does what your question asks, but not what your expected results show:
#echo off
:: Pass the entire line to a subroutine
for /f "delims=" %%a in (notas.txt) do call :process %%a
goto :eof
:process
:: Check if we've evaluated the entire line
if "%1"=="" (
echo No number was found in this line
goto :eof
)
:: Check if the current parameter is only numbers
:: If it is, then echo and move on to the next line
:: If not, use shift to evaluate the next parameter
echo %1|findstr /i /r "[^0-9]" >nul && shift && goto :process
echo %1
goto :eof
2 things. First, I don't know what you want to do if a number isn't found in the line. In the code above I just echo "No number was found in this line".
Second, I presumed "number" to be fully delimited with standard spaces as delimiters. Thus, the code above does NOT return 654321 from the line Compra cfme NFS 654321-CIA CBC because 654321-CIA is not a number delimited by spaces. If you want additional delimiters, then change this line above:
for /f "delims=" %%a in (notas.txt) do call :process %%a
to:
for /f "tokens=1-10 delims=-., " %%a in (notas.txt) do call :process %%a %%b %%c %%d %%e %%f %%g %%h %%i %%j
adding whatever delimiters you want (don't forget a space before the double quote). This line is good for up to 10 entries on a given line. Going up to 26 using tokens=1-26 and %%a through %%z is pretty easy.
Finally -- if you want to pull a non-delimited number -- then that would be a completely different approach. An example would be getting 1356 from PC1356 NLA LOA; or getting 35232 from PC LI-D 35232NDA TTH.
I'm trying to loop a specific file and see if any line contains a certain word or text and I want to replace the whole line. I am not sure how I am suppose to do it.
Right now this what I have:
#echo off
setlocal enabledelayedexpansion
FOR /F "usebackq delims=" %%G IN ("C:\folder\myfile.properties") DO (
Set Line="transaction.sic.lettreEnvironnementBackend"
IF %%G == %Line% (
replace that line with new text
)
)
pause
endlocal
#echo off
setlocal enabledelayedexpansion
Set "Line=transaction.sic.lettreEnvironnementBackend"
(
FOR /F "usebackq delims=" %%G IN ("C:\folder\myfile.properties") DO (
IF "%%G"=="%Line%" (
echo replacement line
) else (echo %%G)
)
)>replacement_filename
pause
endlocal
Note that the replacement filename should not be the source filename. Once tested, move the replacement file to the original filename if required.
Note also that the instruction will exactly match the contents of line - there's no allowance for any other characters on the line.
The syntax SET "var=value" (where value may be empty) is used to ensure that any stray trailing spaces are NOT included in the value assigned.
Also easy in PowerShell.
Get-Content .\OutputFile.txt |
% { if ($_ -eq 'Warning message') { 'NEW WARNING' } else { $_ } }
I would like to create a script that loops reccursively through subfolders of D:\MyFolder\ for example, to find multiple files named MyFile.txt
then look into each file for the keyword FROM and retrieve the string between the FROM and the next semicolon ;.
Sample of MyFile.txt:
LOAD
Thing1,
Thing2,
Thing3,
FROM
Somewhere ;
The desired result is: Somewhere.
(The position of the semicolon ; can be in another line).
I did some tries but I did not succeed in writing a correct script:
#echo off
SET PATH="D:\MyFolder\"
FOR /R %PATH% %%f IN (MyFile.txt) DO (
FOR /F "delims=FROM eol=;" %%A in (%%f) do (
set str=%%A
ECHO %str%
)
)
If it can't be done in batch, please let me know in which language I can do it easily. I would like to have an executable script in the end.
There are some issues in your code:
The delims option of for /F defines characters but not words to be used as delimiter for parsing text files. To find a word, use findstr instead (you could use its /N option to derive the position/line number of the search string).
The eol option of for /F defines a character to ignore a line in case it occurs at the beginning (or it is preceded by delimiters only).
for /R does actually not search for files in case there are no wild-cards (?, *) in the set (that is the part in between parentheses). The dir /S command does, so you can work around this by wrapping a for /F loop around dir /S.
The PATH variable is used by the system to find executables, like findstr, so you must not overwrite it; use a different variable name instead.
Here is the way I would probably do it (supposing any text following the keyword FROM needs to be returned also):
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_ROOT=D:\MyFolder" & rem // (root directory of the tree to find files)
set "_FILE=MyFile.txt" & rem // (name of the files to find in the tree)
set "_WORD=FROM" & rem // (keyword to be searched within the files)
set "_CHAR=;" & rem // (character to be searched within the files)
rem // Walk through the directory tree and find matching files:
for /F "delims=" %%F in ('dir /B /S "%_ROOT%\%_FILE%"') do (
rem // Retrieve the line number of each occurrence of the keyword:
for /F "delims=:" %%N in ('findstr /N /I /R "\<%_WORD%\>" "%%~F"') do (
rem // Process each occurrence of the keyword in a sub-routine:
call :PROCESS "%%~F" %%N
)
)
endlocal
exit /B
:PROCESS
rem // Ensure the line number to be numeric and build `skip` option string:
set /A "SKIP=%~2-1"
if %SKIP% GTR 0 (set "SKIP=skip^=%SKIP%") else set "SKIP="
rem // Read file starting from line containing the found keyword:
set "FRST=#"
for /F usebackq^ %SKIP%^ delims^=^ eol^= %%L in ("%~1") do (
set "LINE=%%L"
setlocal EnableDelayedExpansion
rem // Split off everything up to the keyword from the first iterated line:
if defined FRST set "LINE=!LINE:*%_WORD%=!"
rem /* Split read line at the first occurrence of the split character;
rem the line string is augmented by preceding and appending a space,
rem so it is possible to detect whether a split char. is there: */
for /F "tokens=1,* delims=%_CHAR% eol=%_CHAR%" %%S in (" !LINE! ") do (
endlocal
set "TEXT=%%S"
set "RMND=%%T"
set "ITEM=%~1"
setlocal EnableDelayedExpansion
rem // Check whether a split character is included in the line string:
if not defined RMND (
rem // No split char. found, so get string without surrounding spaces:
set "TEXT=!TEXT:~1,-1!"
) else (
rem // Split char. found, so get string without leading space:
set "TEXT=!TEXT:~1!"
)
rem // Trimm leading white-spaces:
for /F "tokens=*" %%E in ("!TEXT!") do (
endlocal
set "TEXT=%%E"
setlocal EnableDelayedExpansion
)
rem // Return string in case it is not empty:
if defined TEXT echo(!ITEM!;!TEXT!
rem // Leave sub-routine in case split char. has been found:
if defined RMND exit /B
)
endlocal
set "FRST="
)
exit /B
Using the cmd prompt, I am trying to use the findstr feature to output certain criteria from a txt file.
My txt file contains a list of .exe names, including comments. There are alot of them- I want to parse out only the "name.exe" of each line.
Here are examples of different lines in the txt file
C:\\Programme\\Windows Media Player\\mplayer2.exe""=dword:00000000
HOPSTER.EXE; Hopster
Out of these, I want only "mplayer2.exe" and "hopster.exe" to be included in the print out.
Instead, I receive this:
script: findstr "*.exe" Exies.txt
output:
.\Exies.txt:""C:\\Programme\\Windows Media Player\\mplayer2.exe""=dword:00000000
.\Exies.txt:HOPSTER.EXE; Hopster
I was able to pull out some items using this script, findstr /e ".exe" Exies.txt,
but am having trouble with the other examples above.
Any help? Please and thank you.
I don't think you can do it only with findstr (I'm not aware of any FINDSTR output format that would let you print only the matched patterns).
Instead, you could use select-string from PowerShell:
C:\>powershell
Windows PowerShell
Copyright (C) 2009 Microsoft Corporation. All rights reserved.
PS C:\> select-string -Path Exies.txt -Pattern "([a-z0-9]+)\.exe" -AllMatches | % { $_.Matches } | % { $_.Value }
mplayer2.exe
HOPSTER.EXE
PS C:\>
Here is a pure batch solution (there are many explanatory rem-arks in the code, so don't be shocked about the extent).
The most tricky parts are to get the offset position beyond the ".exe" extension for proper truncation of the string read from each line of Exies.txt, and to determine the start position of the file name:
#echo off
setlocal EnableDelayedExpansion
rem regular expression for `findstr` which means:
rem a string consisting of a sub-string with at least one character,
rem NOT containing any of '\?*/:<>"', followed by ".exe";
rem such strings are considered as valid executable file names
set REGEX="[^\\?\*/:<>|\""]*[^\\?\*/:<>|\""]\.exe"
rem parse the output of `findstr` with a `for /F` loop
rem (note that `findstr` is told to do case-insensitive searches)
for /F "tokens=*" %%F in ('findstr /I /R %REGEX% Exies.txt 2^> nul') do (
rem assign a single matching line to variable `LINE`
set "LINE=%%F"
rem call sub-routine to retrieve length of string portion after the
rem (first) occurrence of ".exe"; the length is also equal to the
rem character offset of the string portion after the ".exe" occurrence
call :STRLEN "!LINE:*.exe=!" LEN > nul
rem use another `for` loop to truncate `LINE` after ".exe";
rem so afterwards we have everything up to the ".exe" portion
for %%A in (!LEN!) do (
set "LINE=!LINE:~,-%%A!"
) & rem next %%A
rem replace double-quotes '"' and colons ':' by backslashes '\'
set "LINE=!LINE:"=\!
set "LINE=!LINE::=\!"
rem wrap around another `for` loop to extract the file name portion
rem (this is done to remove any paths from the "*.exe" file name)
for %%B in ("!LINE!") do (
set "LINE=%%~nxB"
) & rem next %%B
rem safety check if extracted "*.exe" file name still matches the
rem regular expression (necessary if ".exe" occurs twice in a line)
echo !LINE! | findstr /I /R %REGEX% > nul 2>&1
if not ErrorLevel 1 (
rem output final "*.exe" file name
echo !LINE!
) & rem end if
) & rem next %%F
endlocal
exit /B
:STRLEN
rem this constitutes a sub-routine to get the length of a string
setlocal EnableDelayedExpansion
set "STR=%~1"
if "%STR%" EQU "" (
set /A LEN=0
) else (
set /A LEN=1
for %%I in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (
if not "!STR:~%%I!" EQU "" (
set /A LEN+=%%I
set "STR=!STR:~%%I!"
) & rem end if
) & rem next %%I
) & rem end if
endlocal & set /A LEN=%LEN%
if not "%~2" EQU "" set %~2=%LEN%
echo %LEN%
exit /B
Assumptions:
the ".exe" portion occurs once per line of Exies.txt only;
the file name consists of at least one character other than these \?*/:<>|";
the file name is delimited to the left by
either a backslash \ or a colon : (meaning that a path has been specified),
or a double-quote " (it might have been enclosed in a pair of such);