I have a .CSV that I am trying to sort through to create another file from the data, but when I run it through, it skips blank entries. For example, if a line is
value,value,value,,,value
and I try to get the 4th column, it would spit out 6th. Presumably because it is the next valid value. I don't want it to skip the blank entry as it can mess up the tables I'm trying to make. Anyone know how to resolve this? (Any tips are welcome as I suck at batch scripts)
Here is my script:
FOR /F "tokens=1,2,3,4,5,6,7,8,9,10,11,12,13,14 delims=," %%a in (file.csv) DO (
echo %%a %%b %%c %%d %%e %%f %%g %%h %%i %%j %%k %%l %%m %%n
)
pause
This is the standard behaviour of the FOR/F loop, consecutive delims only used as one delimiter.
But you can use a workaround with a second FOR/F.
Prefix each column with another character, split the line at the delim and remove the prefix.
setlocal EnableDelayedExpansion
FOR /F "delims=" %%L in (test.bat) DO (
set "line=%%L,,,,,,,,"
set "line=#!line:,=,#!"
FOR /F "tokens=1,2,3,4 delims=," %%a in ("!line!") DO (
set "param1=%%a"
set "param2=%%b"
set "param3=%%c"
set "param4=%%d"
set "param1=!param1:~1!"
set "param2=!param2:~1!"
set "param3=!param3:~1!"
set "param4=!param4:~1!"
echo !param1! !param2! !param3! !param4!
)
)
As jeb already mentiones in his answer, for /F treats consecutive delimiters as one. To avoid that, you could replace each delimiter , by "," and enclose each full line by "", so each field appears as being enclosed within "", which can easily be removed finally by the ~ modifier of the for /F variable; so there is no need to do any more string manipulations (like sub-string expansion) later on:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
for /F "usebackq delims=" %%# in ("file.csv") do (
set "LINE=%%#"
setlocal EnableDelayedExpansion
for /F "tokens=1-4 delims=," %%A in (^""!LINE:,="^,"!"^") do (
endlocal
echo Field 1: %%~A
echo Field 2: %%~B
echo Field 3: %%~C
echo Field 4: %%~D
setlocal EnableDelayedExpansion
)
endlocal
)
endlocal
This might not work properly if the data contain , characters by themselves (remember that , is not considered as delimiter in case a field is enclosed within "" in the original CSV file).
The toggling of delayed expansion is done to not lose any exclamation marks in the data.
Related
I am trying to extract the last word from the last line of a txt file.
The result I want is just Cup$2!.
This is what I tried:
#echo off
SetLocal EnableDelayedExpansion
set L=1
for /F "tokens=2 delims=" %%a in (corner.txt) do (
set line=%%a
if !L!==7 set Line7=%%a
set /a L=!L!+1
)
echo The word is %Line7%
pause
The result I'm getting is The word is.
What should I edit to get the above result?
Get line count.
for /f "tokens=3*" %%i in ('find /c /v /n /i"" corner.txt') do set /a v=%%i-1
Then get the last values from 7-th word of the last line:
for /f "tokens=7*" %%a in ('more corner.txt +%v%') do set "String="%%b""
Variable %String% keeps the values framed by double quotes: Cup&2!/Cup$2!
If you use / as delimiter you can get last value:
for /f "delims=/ tokens=2*" %%a in (%String%) do #echo %%a
Here's a quick example of how you could capture the substring you require, from reading your code, i.e. the last substring on the seventh line:
#Echo Off
SetLocal EnableExtensions DisableDelayedExpansion
Set "var="
For /F UseBackQ^ Skip^=7^ Delims^=^ EOL^= %%G In ("corner.txt") Do Set "var=%%~nxG" & GoTo Next
If Not Defined var GoTo EndIt
:Next
SetLocal EnableDelayedExpansion
Echo The word is !var!
EndLocal
:EndIt
Echo Press any key to close . . .
Pause 1>NUL
EndLocal
Exit /B
If however, as your question title and body asks, you want the last substring of the last line, it's a little bit simpler:
#Echo Off
SetLocal EnableExtensions DisableDelayedExpansion
Set "var="
For /F UseBackQ^ Delims^=^ EOL^= %%G In ("corner.txt") Do Set "var=%%~nxG"
If Not Defined var GoTo EndIt
SetLocal EnableDelayedExpansion
Echo The word is !var!
EndLocal
:EndIt
Echo Press any key to close . . .
Pause 1>NUL
EndLocal
Exit /B
I will not however be explaining any of it, please use the search facility at the top of the page, and the output from the built in help for each of the commands I have used.
Parsing strings in batch files is never ideal but this seems to work:
#echo off
setlocal ENABLEEXTENSIONS DISABLEDELAYEDEXPANSION
echo.line 1 > "%temp%\SO_Test.txt"
echo. >> "%temp%\SO_Test.txt"
echo.foo^&bar!baz hello/world Cup^&2!/Cup$2!>> "%temp%\SO_Test.txt"
goto start
:start
set "line="
for /F "usebackq delims=" %%a in ("%temp%\SO_Test.txt") do #set line=%%a
:word
set "b="
rem Delims is slash, tab, space and the order is important
for /F "tokens=1,* delims=/ " %%A in ("%line%") do (
set "line=%%A"
set "b=%%B"
)
if not "%b%" == "" (
set "line=%b%"
goto word
)
echo.Last word is "%line%"
#ECHO OFF
SETLOCAL
rem The following settings for the source directory, destination directory, target directory,
rem batch directory, filenames, output filename and temporary filename [if shown] are names
rem that I use for testing and deliberately include names which include spaces to make sure
rem that the process works using such names. These will need to be changed to suit your situation.
SET "sourcedir=u:\your files"
SET "filename1=%sourcedir%\q70761955.txt"
SET "amper=&"
FOR /f "usebackq delims=" %%b IN ("%filename1%") DO SET "lastword=%%b"
:: Replace ampersands with spaces, then slashes with spaces
CALL SET "lastword=%%lastword:%amper%= %%"
SET "lastword=%lastword:/= %"
FOR %%b IN (%lastword%) DO SET "lastword=%%b"
ECHO last "word" is -^>%lastword%^<-
GOTO :EOF
Read each line of the file, retaining the last-read line in lastword.
Replace each "delimiter" character (& and / are implied in the question. Others - no information.)
Select the last "word" in the last line.
You would need to change the value assigned to sourcedir to suit your circumstances. The listing uses a setting that suits my system.
I deliberately include spaces in names to ensure that the spaces are processed correctly.
I used a file named q70761955.txt containing your data for my testing.
You could use this in a batch-file run by cmd on windows.
FOR /F "delims=" %%A IN ('powershell -NoLogo -NoProfile -Command ^
"((Get-Content -Path 'corner.txt' -Last 1) -split '[\s*/]')[-1]"') DO (SET "LINE7=%%A")
ECHO %LINE7%
If the script were written in PowerShell instead of cmd language, it could be a one-liner.
$Line7 = ((Get-Content -Path 'corner.txt' -Last 1) -split '[\s*/]')[-1]
I am currently working on a batch script that would move files from a source to a destination based on a master file. I wanted to add a condition that if there is a missing value from the columns then it would skip that line. I tried all the solutions that I found but it won't trigger the if condition. Here's a snippet of the code:
for /f "usebackq tokens=1-3 skip=1 delims=," %%a in (%MasterFilePath%) do (
set dest=%%c
set src=%%b
if !dest!=="" do (echo destination for source !src! is empty)
Here's a sample of the master file:
FolderName,SrcPath,DestPath
FolderA,C:\Work\FolderA,
FolderB,C:\Work\FolderB,C:\Work\Sample\Dest2
FolderC,C:\Work\FolderC,C:\Work\Sample\Dest3
The FOR /F command will always treat consecutive delimiters as one delimiter. So if you delimited file has an empty field and it does not have quotes, the fields will shift to the left on you. So to work around this you can two FOR /F commands. The first one will read in the whole line. That variable is then forced to put quotes around every field using string substitution. You can then use the second FOR /f command to split the fields up into the amount of fields you need.
#ECHO OFF
set "MasterFilePath=file.txt"
FOR /F "usebackq skip=1 delims=" %%G IN ("file.txt") DO (
set "line=%%G"
setlocal enabledelayedexpansion
SET "line="!line:,=","!""
for /f "tokens=1-3 delims=," %%A IN ('echo !line!') do (
IF NOT "%%~B"=="" IF NOT "%%~C"=="" ECHO ROBOCOPY "%%~B" "%%~C"
)
endlocal
)
For the sake of brevity I am just checking if both the source and destination is not empty and then echoing the robcopy command to the screen.
With your input example you would see this.
ROBOCOPY "C:\Work\FolderB" "C:\Work\Sample\Dest2"
ROBOCOPY "C:\Work\FolderC" "C:\Work\Sample\Dest3"
So I'm building a messaging program in batch (I know, it's newbish) and the program takes user input, puts it in my .txt file log.txt, and types it on the screen. I want the output to look like this...
Title
----------------------
contents
of
the
file
here
----------------------
User input here>>
This may seem simple, but the file will be constantly updated by users and I want the program to only display a range of lines to keep that message area stays the same size. I found a simple program to display specific lines, but I can't make them move down one line each time log.txt is changed. Here it is:
#setlocal enableextensions enabledelayedexpansion
#echo off
set lines=1
set curr=1
for /f "delims=" %%a in ('type bob.txt') do (
for %%b in (!lines!) do (
if !curr!==%%b echo %%a
)
set /a "curr = curr + 1"
)
endlocal
(By the way, this program is called lines.bat. I just call it in cmd to test it.)
To return a defined number of lines starting from a certain line number, you can do the following:
#echo off
setlocal EnableExtensions
rem define the (path to the) text file here:
set "TEXT_FILE=log.txt"
rem define the line number here:
set /A "LINE_NUMBER=1"
rem define the number of lines here:
set /A "LINE_COUNT=5"
set /A "LINE_LIMIT=LINE_NUMBER+LINE_COUNT-1"
for /F delims^=^ eol^= %%L in ('findstr /N /R "^" "%TEXT_FILE%"') do (
setlocal DisableDelayedExpansion
set "LINE=%%L"
setlocal EnableDelayedExpansion
for /F "tokens=1 delims=:" %%N in ("!LINE!") do set "LNUM=%%N"
set "LINE=!LINE:*:=!"
if !LNUM! GEQ !LINE_NUMBER! (
if !LNUM! LEQ !LINE_LIMIT! (
echo !LINE!
)
)
endlocal
endlocal
)
endlocal
The findstr command with the /R "^" search pattern returns all lines. The findstr switch /N lets every line precede with a line number (starting from 1) and a colon. The : is used to split the line in two parts: the first part representing the line number is checked whether it is in the range to be returned; the second part is the original line of text which is simply output in case. Even empty lines are taken into account.
You might ask why not simply using the above mentioned : as a delims delimiter option for for /F, but this would cause problems with lines of text starting with :.
The toggling of delayed expansion is necessary to avoid trouble with special characters like !, for instance.
To return the last defined number of lines, the following approach can be used:
#echo off
setlocal EnableExtensions
rem define the (path to the) text file here:
set "TEXT_FILE=log.txt"
rem define the number of lines here:
set /A "LINE_COUNT=5"
for /F "tokens=1 delims=:" %%L in ('findstr /N /R "^" "%TEXT_FILE%"') do (
set /A "LINE_SKIP=%%L"
)
set /A "LINE_SKIP-=LINE_COUNT"
if %LINE_SKIP% GTR 0 (
set "LINE_SKIP=skip^=%LINE_SKIP%^ "
) else (
set "LINE_SKIP="
)
for /F %LINE_SKIP%delims^=^ eol^= %%L in ('findstr /N /R "^" "%TEXT_FILE%"') do (
setlocal DisableDelayedExpansion
set "LINE=%%L"
setlocal EnableDelayedExpansion
set "LINE=!LINE:*:=!"
echo !LINE!
endlocal
endlocal
)
endlocal
Again, the findstr /N /R "^" command is used. But here, we have an additional for /F loop first, which merely counts the number of lines in the text file, extracting the line number preceded by findstr. The second for /F loop is quite similar to the above approach, but a dynamic skip option is introduced, so that the loop starts iterating through the last lines only; the rest is almost the same as above, except that the conditions concerning the current line number have been removed.
I know I could do the counting of lines also by using find /C /V "" rather than looping through the findstr /N /R "^" output, but if there are one or more empty lines at the end of the file, find returns a number one less as the findstr method, so I went for findstr consistently.
Also here, delayed expansion is toggled to avoid trouble with the ! character.
I've successfully written a script which takes a string to search for in a specific file, and then outputs the line where it first occurs, and then I take that value into a for-loop and skips parsing that number of lines and write its contents to a new file. However I do not get blank lines which I find quite problematic to solve.
The string I'm searching for is "/]", caches the line number where it occurs, then accumulates it into a variable with comma-seperation. I then take that variable into a for-loop again and retrieve the first occurring value as my final "skip this number of lines"-variable, which I then use at the bottom for-loop which reads that file again and writes its values to a new files and skips that number of lines at the beginning of the file.
So heres the part of the script that does what I'm describing above:
setlocal enabledelayedexpansion
setlocal enableextensions
set live_svn_access_file=c:\csvn\data\conf\svn_access_file
set of=c:\test.txt
for /f "Tokens=1 Delims=:" %%i in ('findstr /n /c:"/]" %live_svn_access_file%') do (
rem cache value in a variable
set line=%%i
rem accumulate data to one variable
if defined line set skip=!skip!, !line!
)
rem strip first two characters in variable ", "
set skip=!skip:~2!
rem strip everything except first value in array
for /f "Tokens=1 Delims=," %%i in ('echo !skip!') do (
rem store value in a variable
set skip=%%i
)
rem calculate lines - 1 (arithmetic)
set /a skip=!skip!-1
set skip=!skip!
if not defined skip set error=Could not automatically find which parts to skip from live svn_access_file && goto error-handler
for /f "Tokens=* Delims= Skip=%skip%" %%i in (%live_svn_access_file%) do (
rem cache value in a variable
set read-input=%%i
rem write and append content of variable to output-file
echo !read-input! >> %of%
)
I've rewritten the script to match the working one, this is the changed script:
setlocal enabledelayedexpansion
setlocal enableextensions
set live_svn_access_file=c:\csvn\data\conf\svn_access_file
set of=c:\test.txt
for /f "Tokens=1 Delims=:" %%i in ('findstr /n /r /c:"\[..*\]" %live_svn_access_file%') do (
rem cache value in a variable
set line=%%i
rem accumulate data to one variable
if defined line set skip=!skip!, !line!
)
rem take the 2nd sections linenumber into a variable, skipping the first [*foo*]
for /f "Tokens=2 Delims=," %%i in ('"echo !skip!"') do (
rem store value in a variable
set skip=%%i
)
rem add 1 line to skip from retrieved value
set /a skip=!skip!-1
rem verify that number of lines to skip has been successfully retrieved, if not go to error-handler
if not defined skip set error=Could not automatically find which parts to skip from live svn_access_file && goto error-handler
if ["%skip%"] LSS ["1"] set error=Number of lines to skip was less than 1, this will most likely fail && goto error-handler
rem read live svn_access_file, but skip X-lines and write to output-file
setlocal DisableDelayedExpansion
for /f "usebackq Delims= Skip=%skip%" %%i in (`"findstr /n ^^ %live_svn_access_file%"`) do (
rem cache value in a variable
set read-input=%%i
rem write and append content of variable to output-file
setlocal EnableDelayedExpansion
rem strip line number prefix
set read-input=!read-input:*:=!
rem write read lines to output-file
echo(!read-input!>>%of%
endlocal
)
You got two main problems, a FOR /F can't read empty lines (as you discover) and if you use delayed expansion you got trouble with exclamation marks and carets in the line set read-input=%%i.
You can solve both, the empty lines with prefixing each line with a line number by using findstr.
The second with the delayed toggling technic.
SETLOCAL DisableDelayedExpansion
FOR /F "usebackq delims=" %%a in (`"findstr /n ^^ t.txt"`) do (
set "var=%%a"
SETLOCAL EnableDelayedExpansion
set "var=!var:*:=!" This removes the prefix
echo(!var!
ENDLOCAL
)
Is it possible to use a piped stdin stream inside a batch file?
I want to be able to redirect the output of one command into my batch file process.bat list so:
C:\>someOtherProgram.exe | process.bat
My first attempt looked like:
echo OFF
setlocal
:again
set /p inputLine=""
echo.%inputLine%
if not (%inputLine%)==() goto again
endlocal
:End
When I test it with type testFile.txt | process.bat it prints out the first line repeatedly.
Is there another way?
set /p doesn't work with pipes, it takes one (randomly) line from the input.
But you can use more inside of an for-loop.
#echo off
setlocal
for /F "tokens=*" %%a in ('more') do (
echo #%%a
)
But this fails with lines beginning with a semicolon (as the FOR-LOOP-standard of eol is ;).
And it can't read empty lines.
But with findstr you can solve this too, it prefix each line with the linenumber, so you never get empty lines.
And then the prefix is removed to the first colon.
#echo off
setlocal DisableDelayedExpansion
for /F "tokens=*" %%a in ('findstr /n "^"') do (
set "line=%%a"
setlocal EnableDelayedExpansion
set "line=!line:*:=!"
echo(!line!
endlocal
)
Alternatively, on some environments (like WinRE) that don't include findstr, an alternative with find.exe might suffice. find will accept a null search string "", and allows search inversion. This would allow something like this:
#echo off
setlocal DisableDelayedExpansion
for /F "tokens=*" %%a in ('find /v ""') do (
...
The set "line=!line:*:=!" syntax is:
set requires one parameter that is a=b.
If a contains a space or something, you'll have to use the quotation marks around this parameter. Here I don't see any
!line:*:=!
For this syntax, you can type 'set /?' to see the official description on using variables.
!var! is like %var%, to get the value. But !var! means delayed expansion.
line var name
the first : variable modification mark.
**:= **:=(empty), replace the string in the variable's value matches "*:"(virtually from the string start to first : occurence) with (empty), i.e. delete the substring from start to first colon.
FOR /F "tokens=1* delims=]" %%A IN ('FIND /N /V ""') DO (
> CON ECHO.%%B
>> %File% ECHO.%%B
)
Source here: http://www.robvanderwoude.com/unixports.php#TEE
Alternatively, on some environments (like WinRE) that don't include findstr, an alternative with find.exe might suffice. find will accept a null search string "", and allows search inversion. This would allow something like this:
#echo off
setlocal DisableDelayedExpansion
for /F "tokens=*" %%a in ('find /v ""') do (
set "line=%%a"
echo(!line!
)