Skip to main content

How to Delete a Specific Line Number from a File in Batch Script

Programmatically removing a line from a file (for example, deleting a revoked user from a permissions list or removing an old entry from a static routing table) is a core part of system maintenance. Since Batch cannot delete a line in place, we must filter the file: read the original and write everything except the targeted line to a new file.

In this guide, we will demonstrate how to delete a specific line using standard Batch techniques and PowerShell bridging.

Method 1: The "Skip" Loop (Native Batch)

This method iterates through the file and compares the current line number to your target. To ensure we do not accidentally delete empty lines or lines starting with semicolons (a common issue in Batch), we pipe the file through findstr /n, which numbers every line before we process it.

Implementation Script

@echo off
setlocal disabledelayedexpansion

set "Source=UserList.txt"
set "TempFile=%TEMP%\del_line_%RANDOM%.tmp"
set "TargetLine=5"

:: Verify source file exists
if not exist "%Source%" (
echo [ERROR] Source file "%Source%" not found.
pause
exit /b 1
)

:: Validate that TargetLine is a positive number
if %TargetLine% lss 1 (
echo [ERROR] Line number must be 1 or greater.
pause
exit /b 1
)

:: Check if the target line is within the file's bounds
for /f %%C in ('find /c /v "" ^< "%Source%"') do set "TotalLines=%%C"
if %TargetLine% gtr %TotalLines% (
echo [WARNING] Line %TargetLine% exceeds file length ^(%TotalLines% lines^).
echo No changes were made.
pause
exit /b 1
)

echo Deleting line %TargetLine% from "%Source%"...

:: findstr /n "^" prefixes every line with "LineNumber:".
:: This preserves blank lines and lines starting with special characters.
(
for /f "delims=" %%A in ('findstr /n "^" "%Source%"') do (
set "ln=%%A"
setlocal enabledelayedexpansion

:: Remove the line number and the first colon to get the exact original text
set "line=!ln:*:=!"

:: Extract just the line number into %%N
for /f "delims=:" %%N in ("!ln!") do (
:: If this is NOT our target line, write it to the output
if %%N neq %TargetLine% (
echo(!line!
)
)
endlocal
)
) > "%TempFile%"

:: Replace original with the filtered version
move /y "%TempFile%" "%Source%" >nul

if %errorlevel% equ 0 (
echo [SUCCESS] Line %TargetLine% removed from "%Source%".
) else (
echo [ERROR] Failed to replace original file.
pause
exit /b 1
)
pause
exit /b 0
success

Why findstr /n? A standard Batch for /f loop skips blank lines and lines starting with ; (the default end-of-line character). By using findstr /n "^", every line becomes prefixed with a number and a colon (e.g., 5:Text), meaning there are no "blank" lines for the loop to drop. We then cleanly strip the number back out before writing it to the new file.

tip

The script uses the delayed expansion toggle pattern: the raw line is saved to a variable while delayed expansion is disabled (set "ln=%%A") to preserve literal exclamation marks (!) in your text. It is then manipulated and output with delayed expansion enabled (echo(!line!) to safely handle &, |, >, and <.

Method 2: The "Pattern" Deletion (FINDSTR)

If you do not know the exact line number but you know the content of the line, you can use findstr with the /V (invert) switch to exclude it.

@echo off
setlocal

set "Source=users.txt"
set "Dest=users_cleaned.txt"
set "Pattern=REVOKED_USER"

:: Verify source file exists
if not exist "%Source%" (
echo [ERROR] Source file "%Source%" not found.
pause
exit /b 1
)

echo Removing lines containing "%Pattern%" from "%Source%"...

:: /V outputs lines that do NOT match the pattern
:: /C: treats the pattern as a literal string
findstr /V /C:"%Pattern%" "%Source%" > "%Dest%"

if %errorlevel% gtr 1 (
echo [ERROR] Failed to process file (Syntax error or missing file^).
pause
exit /b 1
)

echo [SUCCESS] Filtered output saved to "%Dest%".
pause
exit /b 0
info

The /C: switch ensures the pattern is treated as a literal string. Without it, findstr interprets the pattern as a regular expression, which can cause unexpected results if the text contains ., [, or *. Use /I alongside /V and /C: if you need case-insensitive matching.

Native Batch loops are slow for massive text files. PowerShell handles line-number deletion efficiently using the RemoveAt() method, which avoids the array-slicing pitfalls of index arithmetic.

@echo off
setlocal

set "Source=log.txt"
set "TargetLine=5"

:: Verify source file exists
if not exist "%Source%" (
echo [ERROR] Source file "%Source%" not found.
pause
exit /b 1
)

echo Deleting line %TargetLine% from "%Source%"...

:: ArrayList.RemoveAt() uses a zero-based index, so we subtract 1
:: Wrapping Get-Content in an ArrayList array forces it to handle 1-line files correctly
powershell -NoProfile -Command ^
"$lines = [System.Collections.ArrayList]@(Get-Content -Path '%Source%'); " ^
"$idx = %TargetLine% - 1; " ^
"if ($idx -lt 0 -or $idx -ge $lines.Count) { " ^
" Write-Host '[WARNING] Line %TargetLine% is out of range.'; exit 1 " ^
"} else { " ^
" $lines.RemoveAt($idx); " ^
" $lines | Set-Content -Path '%Source%' -Encoding UTF8 " ^
"}"

if %errorlevel% equ 0 (
echo [SUCCESS] Line %TargetLine% deleted from "%Source%".
) else (
echo [WARNING] No changes were made.
pause
exit /b 1
)
pause
exit /b 0
tip

The ArrayList.RemoveAt() method modifies the list in place at the correct zero-based index. This neatly avoids the typical PowerShell array-slicing approach ($c[0..($idx-1)] + $c[($idx+1)..$c.Length]), which often throws errors at boundary positions (like trying to delete the very first or very last line).

Deleting Multiple Lines

To delete more than one line in a single pass, you can check the line number against a predefined list. Each target must be defined before the loop begins since the line numbers refer to the original file positions.

@echo off
setlocal disabledelayedexpansion

set "Source=TaskList.txt"
set "TempFile=%TEMP%\del_multi_%RANDOM%.tmp"

:: Define lines to delete (use delimiters to enable exact matching)
set "targets=|3|7|12|"

if not exist "%Source%" (
echo [ERROR] Source file "%Source%" not found.
pause
exit /b 1
)

echo Deleting lines 3, 7, and 12 from "%Source%"...

(
for /f "delims=" %%A in ('findstr /n "^" "%Source%"') do (
set "ln=%%A"
setlocal enabledelayedexpansion
set "line=!ln:*:=!"

:: Extract the line number into %%N
for /f "delims=:" %%N in ("!ln!") do (
:: Check if the current line number (%%N) exists in the targets string
if "!targets:|%%N|=!" neq "!targets!" (
rem This line is targeted for deletion - skip it
) else (
echo(!line!
)
)
endlocal
)
) > "%TempFile%"

move /y "%TempFile%" "%Source%" >nul
echo [SUCCESS] Lines removed from "%Source%".
pause
exit /b 0
info

The target list uses pipe delimiters (|3|7|12|) so the check performs an exact match. Without delimiters, trying to delete line 3 would accidentally flag 13, 30, 31, etc.

Notice we use %%N inside the variable substitution (!targets:|%%N|=!). Batch does not support nested delayed expansion (e.g., !targets:|!current!|=!). Injecting the FOR variable %%N directly into the string substitution is the correct, native way to work around this limitation.