The program accepts three arguments: a line number; a filename; and a variable name. The first two arguments are required. If no variable name is specified,
nthline is used by default. Enter the program name on its own for basic usage info.
@echo off & setlocal enableextensions set "a2z=abcdefghijklmnopqrstuvwxyz" & set "nos=0123456789" if "%~1"=="" (goto usage) else set "nth=%~1" if "%~2"=="" (set "errmsg=specify a file name" & goto error ) else set "file=%~2" if "%~3"=="" (set "var=nthline") else ( echo("%~3"| findstr /ix ^"\\"[%a2z%][%a2z%_%nos%]*\\"^" >nul ^ && set "var=%~3" || (set errmsg=^""%~3" is invalid variable name^" goto error)) for /f "tokens=1* delims=0123456789" %%a in ("A0%nth%") ^ do if "%%b" neq "" ( set errmsg=^""%nth%" is not a valid line number^" goto error) if %nth%==0 set /a nth=1 if exist "%file%\\" (set errmsg=^""%file%" is a folder^" goto error) else if not exist "%file%" ( set "errmsg=file "%file%" not found" & goto error) echo("%file%" | findstr "\\* \\?" >nul ^ && (set "errmsg=wildcards (* and ?) not permitted in filename" goto error) for /f "tokens=1" %%c in ('type "%file%" ^| find /c /v ""') ^ do set /a lines=%%c if %lines%==0 (set errmsg=^""%file%" is an empty file^" goto error) if %nth% gtr %lines% ( set "errmsg=specify a line number between 1 and %lines%" goto error) goto nthline :usage (echo(Stores the nth line of a text file in a variable. & echo( echo(Usage: & echo( echo( %~n0 N FileName [Var] & echo( echo(where the contents of line number N of file FileName ^ will be stored in variable& echo(Var. The variable name ^ NthLine is used by default if none is specified.) 1>&2 :error if defined errmsg ^ for /f "delims=" %%e in ("%errmsg%") do echo(%%~e 1>&2 endlocal & exit /b 1 :nthline set /a nth-=1 if %nth% gtr 0 set "opts=skip=%nth% " for /f "%opts%delims=" %%a in ('findstr /n "^" "%file%"') do ( set "line=%%a" setlocal enabledelayedexpansion set "line="!line:*:=!"" for /f "delims=" %%b in ("!line!") do ( endlocal & endlocal & set "%var%=%%~b") if defined %var% (set %var% & exit /b 0 ) else (echo(line %~1 of file "%~2" is blank: ^ variable "%var%" not defined 1>&2 & exit /b 1) )
First, as ever, validity checks are performed on the arguments. The line number must be checked to ensure it consists entirely of digits. To do this, I borrowed a technique I read about on Judago’s SET /P Validation web page. An error also occurs if the line number exceeds the number of lines in the specified file.
Next, the filename is checked for all the usual suspects. Folders, empty or non-existent files, or wildcards in the filename will all throw an error.
Lastly, the variable name needs to be tested for correctness. The
set command is far too liberal about what it regards as an acceptable variable name, imho. For sanity reasons, I had to be much more strict. The program will accept a string beginning with a letter, optionally followed by one or more letters, digits, or underscores.
With all the checks completed, the program skips straight to the specified line number by using the
for /f loop’s
skip option and employing the old
findstr /n trick so that blank lines or lines beginning with semi-colon won’t be skipped.
The requested line is padded with quotes to ensure that it’s not empty. It’s then placed in the
in (...) clause of an inner
for /f loop.
And that’s where things get a little weird. Inside the loop are two
endlocal statements and then an external variable is assigned to the value of the loop variable (with tilde-expansion to eliminate the padded quotes). This technique was developed by Batch guru Jeb. See this message on the alt.msdos.batch.nt newsgroup from November, 2011, for a simple example and explanation.
And there it is! When the program finishes, there should be a variable in the parent environment called
nthline (or whatever name you specified) holding the contents of the nth line of the text file (assuming the line isn’t blank).
Q86 of Timo Salmi’s Batch FAQ shows how to store each line of a text file in a separate variable. However, the numeric portion of the variable name will not match up with the line number if there are any blank lines in the file.
And then there’s this SO thread from May, 2012. It discusses how to store multiple lines from a file in a single variable. Jeb’s solution is a little messier this time, but more pragmatic and much easier to understand.
export flist=`dir /a:-d /b /l /o:n`
will store the output from the
dircommand in the
flistvariable. Remember to use backticks around the command to be exported.