0

This is my script to add data to a specific file:

@echo off

FIND /C /I "foo" %WINDIR%\system32\drivers\etc\hosts
IF %ERRORLEVEL% NEQ 0 ECHO ^8.8.8.8 foo>>%WINDIR%\System32\drivers\etc\hosts

When the file contains new line at the end it is working properly:

from:

data
 

to:

data
8.8.8.8 foo

But when file does not contains new line at the end I am getting connected lines:

from:

data

to:

data8.8.8.8 foo

What should I do to make my code working properly for files with AND without newline at the end?

dafie
  • 951
  • 7
  • 25

4 Answers4

3

I suggest following batch file code for this task:

@echo off
setlocal EnableExtensions DisableDelayedExpansion
set "HostsFile=%SystemRoot%\System32\drivers\etc\hosts"
if not "%ProgramFiles(x86)%" == "" if exist %SystemRoot%\Sysnative\cmd.exe set "HostsFile=%SystemRoot%\Sysnative\drivers\etc\hosts"
if not exist %HostsFile% goto AppendData
%SystemRoot%\System32\findstr.exe /I /L /C:"foo" %HostsFile% >nul
if not errorlevel 1 goto EndBatch
%SystemRoot%\System32\findstr.exe /R /V "$" %HostsFile% >nul
if not errorlevel 1 echo/>>%HostsFile%
:AppendData
>>%HostsFile% echo 8.8.8.8  foo
:EndBatch
endlocal

The third line defines the environment variable HostsFile with standard file path which is right on batch file being executed on 32-bit Windows by 32-bit cmd.exe or on 64-bit Windows by 64-bit cmd.exe in directory %SystemRoot%\System32.

The fourth line takes into account the Windows File System Redirector according to WOW64 Implementation Details. The batch file is executed on 64-bit Windows if there is defined an environment variable with name ProgramFiles(x86) with a non-empty value. But the batch file is executed by 32-bit cmd.exe in directory %SystemRoot%\SysWOW64 if there is %SystemRoot%\Sysnative\cmd.exe. The redirector Sysnative does not exist for x64 applications. In this case the file hosts must be referenced from within 32-bit environment on 64-bit Windows with using the Sysnative redirector in file path.

Next is checked if the file hosts exists at all. If the file does not exist, the data line to append can be directly written to the file without any further checks whereby the file hosts is created in this case.

Otherwise the command FINDSTR is used to search case-insensitive with a literally interpreted search string for foo with redirecting the perhaps found line(s) to device NUL. FINDSTR exits with value 0 if there is at least one positive match and with value 1 if the searched string could not be found on any line.

if not errorlevel 1 means IF exit code is NOT GREATER OR EQUAL 1, or in other words LOWER THAN 1, or in this case EQUAL 0 because of FINDSTR never exits with a negative value as nearly all applications and commands. So if this condition is true, the file hosts contains at least once the searched string and nothing to change on file.

Otherwise FINDSTR is used once more to search this time with a regular expression for end of line and to output all lines not having a line ending because of option /V. So if last line in file hosts has no line ending, FINDSTR exits with value 0 because of having output one line with no line ending whereby this output is redirected to device NUL.

A line ending is appended to file hosts if FINDSTR exited with value 0 because of file hosts ends with no line ending before appending next the data line to add to this file.

The code above does not work if the batch file is not executed with elevated permissions of a local administrator or the file hosts has read-only attribute set or is otherwise protected against modification by a script.

For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.

  • echo /?
  • endlocal /?
  • findstr /?
  • goto /?
  • if /?
  • set /?
  • setlocal /?

See also DosTips forum topic: ECHO. FAILS to give text or blank line - Instead use ECHO/

Mofi
  • 46,139
  • 17
  • 80
  • 143
  • 1
    Note - The logic will fail if the hosts file happens to use 'nix newlines of \n instead of Windows newlines of \r\n. This is because FINDSTR identifies end-of-line as the position before a \r, so the newline will always be appended even if the last line ends with \n. – dbenham Dec 31 '18 at 13:31
2

FINDSTR has an odd quirk that it appends \r\n (carriage return, linefeed) to piped input if the input stream does not end with \n (linefeed). See the Piped and Redirected input may have <CR><LF> appended section at https://stackoverflow.com/a/8844873/1012053 for more info.

You can use this trick to selectively add the missing newline if and only if it is needed.

@echo off
setlocal
set "file=%windir%\system32\drivers\etc\hosts"
findstr /m /i "foo" "%file%" >nul || (
  (
    type "%file%" | findstr "^"
    echo 8.8.8.8 foo
  ) >"%file%.new"
  move /y "%file%.new" "%file%"
)

I chose to use FINDSTR instead of FIND to look for foo, and I use || conditional execution upon error instead of using an IF statement checking ERRORLEVEL.

Limitations when piping input to FINDSTR:

  • The lines must all be <= 8191 bytes long
  • The last line must not be a single character without newline

Bullet proof JREPL.BAT solution

If you have my JREPL utility then there is a "simple" one liner that should always work (I used line continuation to make it easier to read):

findstr /m /i "foo" "%WINDIR%\system32\drivers\etc\hosts" >nul ||^
jrepl "^" "" /jend "output.WriteLine('8.8.8.8 foo')"^
      /f "%WINDIR%\system32\drivers\etc\hosts" /o -

It simply reads each line and writes it out again with \r\n line terminators, and then the /JEND option appends the extra line at the end.

Community
  • 1
  • 1
dbenham
  • 127,446
  • 28
  • 251
  • 390
1

An easier way would be:

@echo off

find /C /I "foo" %windir%\system32\drivers\etc\hosts
if %errorlevel% NEQ 0 (echo. & echo 8.8.8.8 foo)>>%WINDIR%\System32\drivers\etc\hosts

which is one-line and more simple for you.

double-beep
  • 5,031
  • 17
  • 33
  • 41
  • This is fine as long as you don't mind the possibility of an extra unneeded blank line before the last appended line. – dbenham Dec 31 '18 at 14:15
0

If you don't mind another newline/blank line, you can add

ECHO. >>%WINDIR%\System32\drivers\etc\hosts

i.e.

@echo off

FIND /C /I "foo" %WINDIR%\system32\drivers\etc\hosts
IF %ERRORLEVEL% NEQ 0 (
    ECHO.>>%WINDIR%\System32\drivers\etc\hosts
    ECHO 8.8.8.8 foo>>%WINDIR%\System32\drivers\etc\hosts

)
Til
  • 5,150
  • 13
  • 26
  • 34