Removing the very last line-break in a pure batch-file is not quite trivial, not only because there are several limitations that come into account (let us at this point ignore 2 GiB file size limitations).
A core limitation is the fact that command lines, environment variables and echoed out lines are all limited to 8 KiB, so dealing with files that contain longer lines is difficult. However, if one can life with that, here is an approach that uses a for /F
to read the file, echo
to output each line, including the terminating line-break, but, for the very last line, with an end-of-file character (ASCII 0x1A
) inserted before the line-break; that file is then copied by copy
, which is capable of truncating the end-of-character and everything behind, eventually resulting in a file without a final line-break (if the file has got empty lines at the end, leading to multiple consecutive line-breaks, only a single one becomes removed). All this is necessary to retain the original text and to avoid issues with special characters or sequences:
@echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_FILE=%~dp0list.txt" & rem // (path to the file to remove the final line-break from)
set "_CHRF=%~dp0list.chr" & rem // (path to a file that receives a end-of-file character)
set "_TMPF=%~dp0list.tmp" & rem // (path to another temporary file)
rem // Skip processing file if it does not contain a final DOS/Windows-style line-break:
for %%I in ("%_FILE%") do (
findstr /V "$" "%%~I" > nul && (echo Skipping file `%%~nxI`.& exit /B)
< nul set /P ="Processing file `%%~nxI`... "
)
rem // Retrieve end-of-file character:
copy /Y /A nul "%_CHRF%" > nul
for /F "usebackq" %%I in ("%_CHRF%") do set "EOF=%%I"
rem // Write to temporary file:
> "%_TMPF%" (
set "PREV="
rem // Iterate through lines of the file (each of which must be shorter than 8 KiB):
for /F "delims=" %%J in ('findstr /N "^" "%_FILE%"') do (
rem // Output each line but delayed by one loop iteration:
if defined PREV (
setlocal EnableDelayedExpansion
echo(!PREV:*:=!
endlocal
)
set "PREV=%%J"
)
rem // Specifically handle last line by appending an end-of-line character:
setlocal EnableDelayedExpansion
echo(!PREV:*:=!!EOF!
endlocal
)
rem // Copy temporary file over original one regarding the end-of-line character:
copy /Y "%_TMPF%" /A "%_FILE%" /B > nul
echo done.
rem // Clean up unneeded files:
del "%_CHRF%" "%_TMPF%"
endlocal
exit /B
A possible way to avoid the 8 KiB line length limitation is to utilise certutil.exe
and its -encodehex
verb to encode the file as a hexadecimal string, which is then read by set /P
and which can easily be edited since there are no more special characters; the final line-break is removed by deleting the string 0d0a
from the end of the hexadecimal string, whereupon it becomes decoded back to text using the -decodehex
verb of certutil
. Unfortunately, certutil
introduces a file size limitation of a few tens of MiB:
@echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_FILE=%~dp0list.txt" & rem // (path to the file to remove the final line-break from)
set "_HEXF=%~dp0list.hex" & rem // (path to a file that receives the hexadecimal stream)
set "_TMPF=%~dp0list.tmp" & rem // (path to another temporary file)
rem // Skip processing file if it does not contain a final DOS/Windows-style line-break:
for %%I in ("%_FILE%") do (
findstr /V "$" "%%~I" > nul && (echo Skipping file `%%~nxI`.& exit /B)
< nul set /P ="Processing file `%%~nxI`... "
)
rem // Encode file into a hexadecimal stream (there is a size limit of few tens of MiB):
certutil -f -v -encodehex "%_FILE%" "%_HEXF%" 12 > nul
rem // Rounded up integer division; `+2` for final line-break of the hexadecimal stream:
for %%I in ("%_HEXF%") do set /A "LOOP=1+(%%~zI-1+2)/1023"
setlocal EnableDelayedExpansion
rem // Read from hexadecimal file, write into temporary file:
< "!_HEXF!" > "!_TMPF!" (
set "PREV=" & set "CURR="
rem // Iterate one more time as the above integer division of the file size gives:
for /L %%J in (0,1,%LOOP%) do (
rem // Try to read a hexadecimal fragment:
set /P CURR="" && (
rem // Reading successful, hence return fragment from last iteration:
< nul set /P ="!PREV!"
set "PREV=!CURR!"
) || (
rem // Reading failed, so end of data is reached; remove final line-break:
if "!PREV:~-4!"=="0d0a" (
< nul set /P ="!PREV:~,-4!"
) else if "!PREV:~-2!"=="0a" (
< nul set /P ="!PREV:~,-2!" & rem // (this handles Unix-style files)
) else (
< nul set /P ="!PREV!" & rem // (this should normally never be reached)
)
)
)
)
endlocal
rem // Decode temporary file with reduced hexadecimal stream, overwrite original file:
certutil -f -v -decodehex "%_TMPF%" "%_FILE%" 12 > nul
echo done.
rem // Clean up unneeded files:
del "%_HEXF%" "%_TMPF%"
endlocal
exit /B