Windows 2003 batch script with pipe - batch-file

I'm trying to write a batch script that will run every 15 minutes, check number of files in a directory and if that number is bigger then set limit, move all files to another directory. I would easily do this in bash script, but this is my first batch script.
I split this task in several steps:
Find number of files in directory.
I managed to do this with this command:
dir/b/a-d d:\test\test2 | find /v /c "::"
Next thing is to assign output of this command to some variable so I can compare it with my desired limit. This is where problem starts.
ECHO OFF
setlocal enableextensions
FOR /F "tokens=* USEBACKQ" %%a IN (`dir/b/a-d d:\test\test2 ^| find /v /c "::"`)
DO (#SET NUMFIL=%%a)
ECHO %NUMFIL%
endlocal
I'm getting: "| was unexpected at this time". Obviously, pipe is getting in the way. I found that it is special character and as such must be escaped with caret. After doing so, I'm getting: "The syntax of the command was incorrect." This is Windows server 2003.
3.After getting this problem solved, I plan to insert something like this:
IF %%NUMFIL%% > 20
(move "d:\test\test2\ti*" "d:\test\test2\dir\")
That would move all that files (all of them starts with "ti") to desired directory.
So my questions would be: what to do with #2 issue and will #3 work in this case?

Not sure ":: will work in your first case, since :: is unlikely to appear in a DIR output. A single colon would suffice, since the /c option of find counts the number of LINES in which the target string occurs.
The secret to the second problem is that the do keyword must occur on the same line as the closing-parenthesis of the IN clause. It is possible to break the structure into
FOR /F "tokens=* USEBACKQ" %%a IN (
' dir/b/a-d d:\test\test2 ^| find /v /c ":" '
) DO (SET NUMFIL=%%a)
Note the # is not required - it suppresses the command's being echoed, which is turned off by the initial #echo off (the # there suppresses the echoing of the ECHO OFF
Also, the parentheses around this set are not required. IF they are used, the open-parenthesis must occur on the same physical line as the do.
You also don't need to use usebackq since you have no need here to change the interpretation of quotes.
Third item - > is a redirector. For a comparison operator, use one of EQU GEQ LSS LEQ GTR NEQ depending on comparison required.
And again, the open-parenthesis must be on the same line as the if. With an else, the close-parenthsis before the ELSE , the ELSE keyword and the open-parenthesis after must all be on the same physical line.

I think this should work. Note that it does not use backticks.
ECHO OFF
setlocal enableextensions
FOR /F %%a IN (' dir /b /a-d "d:\test\test2" ^| find /c /v "" ') DO SET NUMFIL=%%a
ECHO %NUMFIL%
IF %NUMFIL% GTR 20 (move "d:\test\test2\ti*" "d:\test\test2\dir\")

Related

how to add new lines to windows hosts with a batch file

i know this was already discussed but i didn't find what i needed.
I need to add new lines at the end of the hosts window file but,
first i need to check if these lines already exist and than adding them.
I tried this:
set "list=examp.com=examp2.com=examp3.com"
SET NEWLINE=^0.0.0.0
for %%a in (%list%) do (
FINDSTR /I %%a %WINDIR%\system32\drivers\etc\hosts)
IF %ERRORLEVEL% NEQ 0 (ECHO %NEWLINE% %%a>>%WINDIR%\System32\drivers\etc\hosts)
pause
but the result in hosts is just 1 line like this:
0.0.0.0 %a
I also want to know if it's possible to change this:
set "list=examp.com=examp2.com=examp3.com"
with another code that will take variables from a txt file.
Your code is not quite as bad as Mofi would suggest. Although it's quite uncommon to use an equal sign as a delimiter for a for loop, it is nevertheless legal syntax. The largest two problems I see are that you're closing your for loop at the end of your findstr statement; and, assuming you fix that, %ERRORLEVEL% would need its expansion delayed. Or you could use the if errorlevel syntax of the if statement (see help if in a cmd console for full details`). Or even better, use conditional execution.
Here's an example using conditional execution. This example also opens your HOSTS file for appending one time, rather than one time for each loop iteration -- a subtle efficiency improvement, true, but a worthwhile habit to practice when writing files with a loop. And because HOSTS by default has attributes set to prevent writing, I stored and removed the read-only / system / hidden / etc. attributes of the hosts file, appended the changes to the file, then restored the attributes back the way they were before.
#echo off & setlocal
set "hosts=%WINDIR%\system32\drivers\etc\hosts"
set "list=examp.com=examp2.com=examp3.com"
SET "NEWLINE=0.0.0.0"
for /f "delims=" %%I in ('attrib "%hosts%"') do set "raw=%%~I"
setlocal enabledelayedexpansion
for /L %%I in (0,1,18) do if not "!raw:~%%I,1!"==" " set "attrs=!attrs!+!raw:~%%I,1! "
endlocal & set "attrs=%attrs%"
attrib -h -s -r -o -i -x -p -u "%hosts%"
>>"%hosts%" (
for %%a in (%list%) do (
>NUL 2>NUL find /I "%%a" "%hosts%" || echo(%NEWLINE% %%a
)
)
attrib %attrs% "%hosts%"

Counting files with FOR (command batch)

I want count only files in a directory with Windows batch.
(The ultimate purpose is to call a vbs file if I have any files whatsoever.)
Here's what I have so far:
set /a db=0
echo %db%
for /f %%i in ('dir /b') do (
set /a db=%db%+1
echo %db%
)
echo %db%
This will me give the following: 0 0 0 0 1 for the value of %db%
(as I have 3 files in the directory right now)
Maybe trivial, but why won't it increase the value of %db% during the loop, only in the end?
What happens between the last loop of for (where %db% still was 0) and the last line (where %db% is 1 already)?
How should I fix it?
Thanks in advance!
While the batch file is being executed, each line or block of lines (lines enclosed in parenthesis) is first parsed and then executed. During the parse phase, variable read operations (where you retrieve the value of the variable, that is %var%) is removed, being replaced with the value inside the variable. Once this is done, the resulting command is executed.
Inside your for loop you are changing the db variable, but you can not retrieve the changed value. All the read operations were replaced with the value in the variable before the for command start to execute.
The usual way to solve this problem is to enable delayed expansion and change the %var% syntax into !var! syntax where needed. This tells the batch parser that the variable/value substitution must be delayed until the command is executed, not when the line/block is parsed.
setlocal enabledelayedexpansion
set /a db=0
echo %db%
for /f %%i in ('dir /b') do (
set /a db=!db!+1
echo !db!
)
echo %db%
Now the read operations inside the for loop are using delayed expansion.
But, you don't even need it. The set /a uses its own parser that is able to retrieve the value in the referenced variables, so you can use any of those options
set /a db=db+1
set /a db+=1
to change the variable without having to use read syntax.
Also, unless you need to take into consideration hidden files, it is better to not use a for /f processing the output of a dir command that is executed in a separate cmd instance. Just use a for loop
set /a "db=0"
for %%a in (*) do set /a "db+=1"
echo %db%
But, if as you point all you need is to know if you have any file, and not the number of them, all this is not even needed
dir /a-d >nul 2>nul && ( echo there are files ) || ( echo there is not any file )
It just executes the dir command, with folders excluded (/a-d), discarding any output (>nul) or error message (2>nul).
- If any file is found (no errors), the command after the conditional operator && is executed.
- If there is not any file, the dir command fails and the command after the || conditional operator is executed.
If you just want to detect whether or not there are any files, MC ND already showed a reliable way in their answer.
To count the number of files you can let the find command do the work, as it features a /C switch that counts the number of matching lines. If you specify to do an inverse search by /V (so to return all non-matching lines), together with an empty search string "", all lines are returned.
So when the input for find comes from a dir command line that lists all files, the total count of files is returned (2> nul suppresses the error message in case no files are present):
2> nul dir /B /A:-D | find /C /V ""
To capture the count in a variable, use a for /F loop:
for /F %%C in ('
2^> nul dir /B /A:-D ^| find /C /V ""
') do set "COUNT=%%C"
echo %COUNT%
Note that special characters like > and | need to be escaped by preceding with ^ in order for them not to be processed immediately but in the cmd instance initiated by for /F.

CMD Batch - Find literal string

Got an interesting problem. I'm pulling a query on a registry to see if it exists - if it does, I go down one path. If it doesn't, I go down a seperate path. I'll put a code example at the bottom. Here's the question:
FIND doesn't have an /L switch I can use for literal interpretation. One of my settings is 4, and the other is 14. So 14 works great, 4 finds both 4 and 14.
Is there a way to have it find "4" rather than just 4? Using escape characters before the quotations or double quoting just makes it miss entirely.
This is just an example of what I mean. Names and values changed to protect the innocent. This is part of a much larger script, so 'enabledelayedexpansion' is necessary.
setlocal enabledelayedexpansion
set ValueOne=4
set ValueTwo=14
rem This section determines if additional specific concept settings are needed.
:ConceptCheck
rem DEBUG
echo CONCEPT CHECK
for /f "tokens=1" %%G in ('reg query hklm\software\SomeRegistry\ /v "Value" ^| find "!ValueTwo!"') do (
echo Value Two Found!
call :ValueTwoSetup)
for /f "tokens=1" %%G in ('reg query hklm\software\SomeRegistry\ /v "Value" ^| find "!ValueOne!"') do (
echo Value One Found!
call :ValueOneSetup)
goto :eof
When run, if the entry is ValueTwo, it works great. ValueOne calls both ValueTwo and itself, due to the fact that 14 has the number 4 in it, and find catches it. I've tried ""!ValueOne!"", but then it fails utterly. I've tried "^"!ValueOne!^"" to the same result. I CAN stick an 'goto :eof' at the end of the do call, but then I lose expandability, and I still have to monitor which value shows up first, which makes things more complicated and clumsy. I'll be dealing with around two hundred 'values' by the time I'm done, and they aren't sequential.
Thoughts?
findstr has \< and \> special characters to indicate word boundaries:
findstr /r /c:"\<!ValueOne!\>"
Alternatively you can instruct reg query to use exact match by adding /e, so no need for find:
reg query hklm\software\SomeRegistry /s /v "Value" /d /e /f "!ValueOne!" >nul
if not errorlevel 1 echo ValueOne Found! & call :ValueOneSetup

Get registry key value and change accordingly from multiple PCs

First thank you for this great site! I've learned lots of batch scripting from here, but finally got stuck. I was tasked to write a script that will go out and check a specific registry keyword and change the ones that are not correct, on all PCs on the network.
#echo off
SetLocal EnableDelayedExpansion
FOR /F %%a in (C:\batchFiles\computers.txt) DO (
FOR /F "tokens=3" %%b in (reg query "\\%%a\HKLM\SOFTWARE\some\any" /v "Forms Path") do set "var=%%b"
if "%var%" == "\\server\folder\forms\path"
echo %%a was correct
pause
if "%var%" NEQ "\\server\folder\forms\path"
echo %%a was not correct
pause
)
My boss tasked me with this not to long ago and its a little above my head, so i'm trying to learn on the fly. I tried with %errorlevel% and couldn't get it to do what I wanted either.
I had all of my PC names listed in C:\batchFiles\computers.txt. The REG_SZ key from "Forms Path" is a folder located on a network drive. Right now it says that the syntax is incorrect.
If you can understand what i'm trying to do, and have a better suggestion, I'm all ears! Oh and I'd like to output ALL of the results to a text file so I know which PCs were changed, which ones had it correct, and which ones the script couldn't reach.
Thank you so much for your time!
You enabled delayed environment variable expansion, but do not use it. %var% must be written as !var! to make use of delayed expansion as required here.
The syntax used on both if conditions is also not correct.
The registry query output by reg.exe on my computer running Windows XP is:
! REG.EXE VERSION 3.0
HKEY_LOCAL_MACHINE\SOFTWARE\some\any
Forms Path REG_SZ \\server\folder\forms\path
There is first a blank line, next a line with version of reg.exe, one more blank line, a line with registry key and finally on fifth line the data of interest. Therefore I used in the batch code below skip=4 to speed it up. However, the inner loop would produce the right result also without skip=4 and therefore parsing all 5 lines.
Important is the last line. The inner loop separates by spaces. As the name of the registry value contains also a space character, the first two tokens are for Forms and Path. And the third token is REG_SZ.
The rest of the line after the spaces after REG_SZ is of real interest, but could contain also a space character. So I used in batch code below not tokens=4, but instead tokens=3* and ignored %b which holds REG_SZ. Instead %c is assigned to environment variable var resulting in getting really entire string value even if the string contains 1 or more spaces.
And the environment variable var is deleted before a new query on next computer is executed in case of a computer does not contain the registry value at all. The error message written by reg.exe to stderr is redirected to device nul for this case. The value of var would be unchanged from previous computer if not deleted before running the next query.
#echo off
setlocal EnableDelayedExpansion
for /F %%a in (C:\batchFiles\computers.txt) do (
set var=
for /F "skip=4 tokens=3*" %%b in ('%SystemRoot%\System32\reg.exe query "\\%%a\HKLM\SOFTWARE\some\any" /v "Forms Path" 2^>nul') do set "var=%%c"
if "!var!" == "\\server\folder\forms\path" (
echo %%a has correct value.
) else if "!var!" == "" (
echo %%a does not have the value at all.
) else (
echo %%a has wrong value.
)
pause
)
endlocal

CMD.EXE batch script to display last 10 lines from a txt file

Any ideas how to echo or type the last 10 lines of a txt file?
I'm running a server change log script to prompt admins to state what they're doing, so we can track changes. I'm trying to get the script to show the last 10 entries or so to give an idea of what's been happening recently. I've found a script that deals with the last line, as shown below, but can't figure out what to change in it to display the last 10 lines.
Script:
#echo off
setLocal EnableDelayedExpansion
for /f "tokens=* delims= " %%a in (c:\log09.txt) do (
set var=%%a
)
echo !var!
Example of log file:
06/02/2009, 12:22,Remote=Workstation-9,Local=,
mdb,bouncing box after updates,CAS-08754,
=================
07/02/2009, 2:38,Remote=,Local=SERVER1,
mdb,just finished ghosting c drive,CAS-08776,
=================
07/02/2009, 3:09,Remote=,Local=SERVER1,
mdb,audit of server,CAS-08776,
Any thoughts?
The script works great, just need it to pipe more lines to the screen.
Hopefully this will save Joel's eyes :)
#echo OFF
:: Get the number of lines in the file
set LINES=0
for /f "delims==" %%I in (data.txt) do (
set /a LINES=LINES+1
)
:: Print the last 10 lines (suggestion to use more courtsey of dmityugov)
set /a LINES=LINES-10
more +%LINES% < data.txt
This answer combines the best features of already existing answers, and adds a few twists.
The solution is a simple batch implementation of the tail command.
The first argument is the file name (possibly with path information - be sure to enclose in quotes if any portion of path contains spaces or other problematic characters).
The second argument is the number of lines to print.
Finally any of the standard MORE options can be appended: /E /C /P /S /Tn. (See MORE /? for more information).
Additionally the /N (no pause) option can be specified to cause the output to be printed continuosly without pausing.
The solution first uses FIND to quickly count the number of lines. The file is passed in via redirected input instead of using a filename argument in order to eliminate the printout of the filename in the FIND output.
The number of lines to skip is computed with SET /A, but then it resets the number to 0 if it is less than 0.
Finally uses MORE to print out the desired lines after skipping the unwanted lines. MORE will pause after each screen's worth of lines unless the output is redirected to a file or piped to another command. The /N option avoids the pauses by piping the MORE output to FINDSTR with a regex that matches all lines. It is important to use FINDSTR instead of FIND because FIND can truncate long lines.
:: tail.bat File Num [/N|/E|/C|/P|/S|/Tn]...
::
:: Prints the last Num lines of text file File.
::
:: The output will pause after filling the screen unless the /N option
:: is specified
::
:: The standard MORE options /E /C /P /S /Tn can be specified.
:: See MORE /? for more information
::
#echo OFF
setlocal
set file=%1
set "cnt=%~2"
shift /1
shift /1
set "options="
set "noPause="
:parseOptions
if "%~1" neq "" (
if /i "%~1" equ "/N" (set noPause=^| findstr "^") else set options=%options% %~1
shift /1
goto :parseOptions
)
for /f %%N in ('find /c /v "" ^<%file%') do set skip=%%N
set /a "skip-=%cnt%"
if %skip% lss 0 set skip=0
more +%skip% %options% %file% %noPause%
You should probably just find a good implementation of tail. But if you really really insist on using CMD batch files and want to run on any NT machine unmolested, this will work:
#echo off
setLocal EnableDelayedExpansion
for /f "tokens=* delims= " %%a in (c:\tmp\foo.txt) do (
set var9=!var8!
set var8=!var7!
set var7=!var6!
set var6=!var5!
set var5=!var4!
set var4=!var3!
set var3=!var2!
set var2=!var1!
set var1=!var!
set var=%%a
)
echo !var9!
echo !var8!
echo !var7!
echo !var6!
echo !var5!
echo !var4!
echo !var3!
echo !var2!
echo !var1!
echo !var!
There are several windows implementations of the tail command. It should be exactly what you want.
This one sounds particularly good:
http://malektips.com/xp_dos_0001.html
They range from real-time monitoring to the last x lines of the file.
Edit: I noticed that the included link is to a package It should work, but here are some more versions:
http://www.lostinthebox.com/viewtopic.php?f=5&t=3801
http://sourceforge.net/projects/tailforwin32
If file is too large it can take too long to get count of lines
another way is to use find and pass it a nowhere string
$find /v /c "%%$%!" yourtextfile.txt
this would result an output like this
$---------- yourtextfile.txt: 140
then you can parse output using for like this
$for /f "tokens=3" %i in ('find /v /c "%%$%!" tt.txt') do set countoflines=%i
then you can substract ten lines from the total lines
After trying all of the answers I found on this page none of them worked on my file with 15539 lines.
However I found the answer here to work great. Copied into this post for convenience.
#echo off
for /f %%i in ('find /v /c "" ^< C:\path\to\textfile.txt') do set /a lines=%%i
set /a startLine=%lines% - 10
more /e +%startLine% C:\path\to\textfile.txt
This code will print the last 10 lines in the "C:\path\to\textfile.txt" file.
Credit goes to OP #Peter Mortensen
using a single powershell command:
powershell -nologo "& "Get-Content -Path c:\logFile.log -Tail 10"
applies to powershell 3.0 and newer
I agree with "You should use TAIL" answer. But it does not come by default on Windows. I suggest you download the "Windows 2003 Resource Kit" It works on XP/W2003 and more.
If you don't want to install on your server, you can install the resource kit on another machine and copy only TAIL.EXE to your server. Usage is sooooo much easier.
C:\> TAIL -10 myfile.txt
Here's a utility written in pure batch that can show a lines of file within a given range.To show the last lines use (here the script is named tailhead.bat):
call tailhead.bat -file "file.txt" -begin -10
Any ideas how to echo or type the last
10 lines of a txt file?
The following 3-liner script will list the last n lines from input file. n and file name/path are passed as input arguments.
# Script last.txt
var str file, content ; var int n, count
cat $file > $content ; set count = { len -e $content } - $n
stex -e ("["+makestr(int($count))) $content
The script is in biterscripting. To use, download biterscripting from http://www.biterscripting.com , save this script as C:\Scripts\last.txt, start biterscripting, enter the following command.
script last.txt file("c:\log09.txt") n(10)
The above will list last 10 lines from file c:\log09.txt. To list last 20 lines from the same file, use the following command.
script last.txt file("c:\log09.txt") n(20)
To list last 30 lines from a different file C:\folder1\somefile.log, use the following command.
script last.txt file("C:\folder1\somefile.log") n(30)
I wrote the script in a fairly generic way, so it can be used in various ways. Feel free to translate into another scripting/batch language.

Resources