Ensure Text Files End with a Newline

Posted On Thu, 4 Oct 2012

Filed under Batch
Tags: , , , ,

Comments Dropped leave a response

From time to time, you may find yourself building up a file comprised of command output and snippets from other files. On these occasions, you will no doubt be using pipes and redirection, and commands such as copy, findstr, and type.

Which is all well and good except that you could easily run into trouble if some of the bits and pieces you're cobbling together end with a newline while others don't. For instance, type will join the last line of a file that doesn't end with a newline with the first line from the next file, forming one long line. Probably not what you want.

And according to Dave Benham in his treatise on the Undocumented Features and Limitations of FindStr, findstr under XP and Win7 will hang if redirected input doesn't end with a linefeed character.

Program

Below is a little Batch program that appends a Windows newline sequence (<CR><LF>) to the end of a text file. Files that already end with a newline will not be modified.

The program accepts a list of one or more filenames from the command line. Wildcards (? and *) are permitted (eg, "*.txt" or "*.htm?"). Folders and empty files will be skipped.

@echo off & setlocal enableextensions
set /a allfiles=0,valfiles=0,newlines=0

if "%~1"=="" (
echo(specify a file or list of files ^(wildcards permitted^) 1>&2
set /a allfiles=1 & goto end)

:loop
for %%f in ("%~1") do (set /a allfiles+=1,match=1
if not exist "%%~f" (echo("%%~f" not found 1>&2 & goto break)
if exist "%%~f\" (echo("%%~f" is a folder 1>&2 & goto break)
set /a valfiles+=1
if not %%~zf==0 (findstr /v $ "%%~f" >nul ^
&& (set /a newlines+=1 & echo(>>"%%~f")
) else echo("%%~f" is an empty file 1>&2
)

:break
if not defined match (set /a allfiles+=1
echo("%~1" did not match any files in current folder 1>&2)
set match=
if "%~2" neq "" (shift /1 & goto loop)

:end
if %valfiles% gtr 0 ^
echo(appended newline to %newlines%/%valfiles% files 1>&2
set /a code=!!(allfiles-valfiles)
endlocal & exit /b %code%

Discussion

The command line parameters are read in one at a time and passed to a simple for loop. The for loop is preferred because:

  • it can cope with filenames containing Unicode characters
  • folder names will be automatically excluded
  • if no files in the current folder match a parameter containing wildcards (eg, "*.tmp"), the body of the loop will not be executed

But filenames containing no wildcard characters will get through. That's why we have to test for the non-existence of every file as well as the existence of a folder by the same name.

Next, we check if the file is empty. If it isn't, we test the file to see whether or not it ends with a newline. This is done using the findstr /v $ command. Which means output any lines in the file that don't (the /v switch inverts the command) match the end-of-line marker ($).

If the test is true (ie, the file doesn't end with a newline), a newline is appended to the end of the file (echo(>>"%%~f").

And that's essentially it. The rest is for flagging errors and setting the program's exit status (exit /b %code%). The exit status is stored in the special %errorlevel% environment variable. A value of 0 means no errors occurred whereas 1 means errors were encountered.

Note that doing the opposite (removing a newline from the end of a file) is a whole other box of crayons and there are no simple pure Batch solutions. It might be the topic of a post in the far distant future 😉

Related Links

  • Q163 of Prof. Timo Salmi's Batch FAQ.
  • This alt.msdos.batch.nt thread from June, 2012.
  • The tiny trunc utility which will shorten (or lengthen) a file to a specified size in bytes.
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