使用start子命令改进循环的批处理文件

时间:2016-12-03 22:34:10

标签: powershell batch-file

我目前有一个非常简单的脚本,它会ping 10个IP:

@ECHO OFF
for /L %%i in (100, 1, 110) DO (
  START /W /B cmd /c ping -n 1 192.168.0.%%i | find "time="
)

输出符合预期:

C:\Users\herpderp>test.bat
Reply from 192.168.0.101: bytes=32 time=294ms TTL=64
Reply from 192.168.0.104: bytes=32 time=1ms TTL=64

然而,它非常缓慢并且肯定会顺序发生。当我使用PowerShell Measure-Command运行时,我得到了以下结果:

PS C:\Users\derpherp> measure-command {start-process test.bat -Wait}

Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 23
Milliseconds      : 107
Ticks             : 231074173
TotalDays         : 0.000267446959490741
TotalHours        : 0.00641872702777778
TotalMinutes      : 0.385123621666667
TotalSeconds      : 23.1074173
TotalMilliseconds : 23107.4173

所以我们看到它需要大约23秒才能执行。

现在,如果我在Linux系统上并希望做同样的事情,我可以做到以下几点:

#!/bin/bash

for ip in $(seq 100 110); do
  ping -c 1 192.168.0.$ip | grep "bytes from" | cut -d" " -f4 | cut -d ":" -f1 &
done

结果有多种不同:

  • 结果并不总是顺序的,这意味着它实际上更加异步地启动命令:

    root@kali:~/vids/bash_scripting# ./test.sh
    192.168.0.104
    192.168.0.100
    192.168.0.103
    192.168.0.101
  • 显示所有正面结果的时间通常不到一秒,这可以扩展到更大的数字集。

所以,我的问题是,这是批量脚本的限制吗?我觉得很难相信bash在执行如此简单的任务时比Windows CMD解释器的表现要好几十倍?

如果这是有限的,PowerShell是否提供了创建任务的有竞争力的方法?我在foreach循环中使用了Start-Job但是对于大量任务(即/ 16网络上的Test-Connection)似乎变得不可行了

修改1

@ECHO OFF
for /L %%i in (100, 1, 110) DO (
  ping -n 1 192.168.0.%%i | find "time="
)

这将输出当前窗口,并且与初始变量

一样长
@ECHO OFF
for /L %%i in (100, 1, 110) DO (
  START ping -n 1 192.168.0.%%i | find "time="
)

这将输出到每个IP的唯一窗口,并且需要很长时间才能完成。

3 个答案:

答案 0 :(得分:4)

异步并不容易,但使用PowerShell可以很容易地完成工作。

此代码应该表现出您的bash代码的样子,返回所有响应的IP地址而不会出错:

$IPAddresses = 100..110 | ForEach-Object { "192.168.0.$_"; }

$JobList = $IPAddresses | ForEach-Object {
    Test-Connection -ComputerName $_ -Count 1 -AsJob;
}

Receive-Job -Job $JobList -AutoRemoveJob -Wait | `
    Where-Object { $_.StatusCode -eq 0 } | `
    Select-Object -ExpandProperty Address;

以上完成了我在3秒钟内对抗~250个主机。

Test-Connection本身声称最多可同时使用32个连接(可使用-ThrottleLimit设置进​​行配置)但如果您要定位,-Delay选项似乎完全会覆盖该设置范围很广。

PowerShell v4及更早版本可能存在问题,因为它的行为可能略有不同。 Test-Connection特别是在不同版本的Windows或PowerShell上似乎是特殊的。至少,我总是记得抛出错误,而不是在以前的版本中返回错误状态代码。

如果你想要比Test-Connection给你的更好的谷物控制 - 例如,它默认为1秒超时,所以你在任何主机关闭时等待的最短时间是1第二 - 你可能不得不恢复直接呼叫Get-WMIObject -Query "SELECT * FROM Win32_PingStatus WHERE Address = '$IP' AND TimeOut = 200" -AsJob

答案 1 :(得分:2)

似乎我找到了一个纯粹的解决方案。基本上,脚本同时触发几个ping命令,这些命令都将结果写入各个日志文件;监视这些文件的写访问权限,因为只要相应的ping进程正在进行,文件就会被写锁定;只要授予写访问权限,包含IP地址的日志文件的第一行就会被复制到摘要日志文件中。所以这是代码 - 请参阅所有解释性rem备注:

@echo off
setlocal EnableExtensions DisableDelayedExpansion

rem // Predefine IP addresses as an array:
for /L %%J in (0,1,5) do (
    set "IP[%%J]=127.0.0.%%J"
)

rem // Ping IP addresses simultaneously, create individual log files:
for /F "tokens=2,* delims=[=]" %%J in ('set IP[') do (
    start "" /B cmd /C ping -n 2 %%K ^| find "TTL=" ^> "%~dpn0_%%J.log"
)

rem // Deplete summary log file:
> "%~dpn0.log" rem/

rem /* Polling loop to check whether individual log files are accessible;
rem    this works, because a pinging process is not yet finished, the
rem    respective log file is still opened and therefore write-locked: */
:POLL
rem // Give processor some time:
> nul timeout /T 1 /NOBREAK
rem /* Loop through all available array elements; for every accomplished
rem    pinging process, the related array element becomes deleted, so
rem    finally, there should not be any more array elements defined: */
for /F "tokens=2 delims=[=]" %%J in ('2^> nul set IP[') do (
    rem // Suppress error message in case log file is write-locked:
    2> nul (
        rem // Try to append nothing to the log file:
        >> "%~dpn0_%%J.log" rem/ && (
            rem /* Appending succeeded, hence log file is no longer locked
            rem    and the respective pinging process has been finished;
            rem    therefore read the first line containing the IP: */
            set "FILE=" & set "IP="
            < "%~dpn0_%%J.log" set /P IP=""
            rem // Copy the read line to the summary log file:
            if defined IP >> "%~dpn0.log" call echo(%%IP%%
            rem // Undefine related array element:
            set "IP[%%J]="
            rem // Store log file path for later deletion:
            set "FILE=%~dpn0_%%J.log"
        )
        rem // Delete individual log file finally:
        if defined FILE call del "%%FILE%%"
    )
)
rem // Jump to polling loop in case there are still array elements:
> nul 2>&1 set IP[ && goto :POLL

endlocal
exit /B

答案 2 :(得分:1)

您不需要start命令。你关闭了它的所有功能。然后你告诉它为CMD开始一个新的过程,这个过程很慢且没必要。所以 - ping -n 1 192.168.0.%%i |find "time="

请参阅我的摘要,了解如何启动程序 - What do all commands in batch mean and do?

您可以使用start并行运行ping,但是您需要删除/w,这意味着等待(但因为被忽略)和/b #39;在新生成的进程中运行该命令,但是按顺序执行。

由于您仍在进行Ping的多个流程创建,因此您可以使用WMIC来避免这种情况并测试一个流程中的所有计算机。

wmic /node:@"c:\computerlist.txt" /append:"textfile.txt" path win32_pingstatus where "address='127.0.0.1' and responsetime > 100" get responsetime,timestamprecord

所以我们已经从20个流程创建到1。

在编程中,我们必须支付系统开销税。 Windows在Process Creation和Window Creation中收取大部分税。这是为了允许在没有太多系统开销的情况下进行其他操作。一个例子是Windows保留正在运行的进程表 - 在创建进程时使用资源进行更新,但允许快速查找其他操作。