Store Nth Line of a Text File in a Variable

Posted On Sat, 10 Nov 2012

Filed under Batch
Tags: , , ,

Comments Dropped leave a response

This question crops up from time to time: “How do I save a line from a file into a variable?” It seems like a perfectly reasonable question to ask. You might think the answer is equally as straightforward. Until you try to write a Batch program to do it, that is…

Program

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)
)

Discussion

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 wasn't blank).

If you found this post useful or interesting, I'd be interested in your feedback. Either leave a comment, or try one of the options under the “Contact the Author” heading on the front page.

Related Links

  • 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.

  • Read Jeb's “bullet proof solution” in the Stack Overflow thread Make an Environment Variable Survive ENDLOCAL from July, 2010. Brilliant, but mind-bending. Strictly for wizards!

  • 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.

  • Try the free export utility (32 and 64-bit versions available) from XEOX. It enables you to store output from a command in an environment variable. For example:

    export flist=`dir /a:-d /b /l /o:n`

    will store the output from the dir command in the flist variable. Remember to use backticks around the command to be exported.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s