使用 start 子命令改进循环的批处理文件 [英] Improving Batch File for loop with start subcommand
问题描述
我目前有一个非常简单的脚本,可以 ping 10 个 IP:
@ECHO 关闭对于/L %%i 在 (100, 1, 110) DO (开始/W/B cmd/c ping -n 1 192.168.0.%%i |找到时间=")
输出符合预期:
C:Usersherpderp>test.bat192.168.0.101回复:bytes=32 time=294ms TTL=64192.168.0.104回复:bytes=32 time=1ms TTL=64
但是,它非常缓慢并且肯定是按顺序发生的.当我使用 PowerShell Measure-Command
运行它时,我得到以下结果:
PS C:Usersderpherp> measure-command {start-process test.bat -Wait}天数:0小时 : 0分钟:0秒数:23毫秒:107滴答声:231074173总天数:0.000267446959490741总小时数:0.00641872702777778总分钟数:0.385123621666667总秒数:23.1074173总毫秒数:23107.4173
所以我们看到执行需要大约 23 秒.
现在,如果我在 Linux 系统上并且想做同样的事情,我可以执行以下操作:
#!/bin/bash对于 $(seq 100 110) 中的 ip;做ping -c 1 192.168.0.$ip |grep "来自的字节" |剪切 -d" " -f4 |cut -d ":" -f1 &完毕
结果以多种方式不同:
结果并不总是连续的,这意味着它实际上以更异步的方式启动命令:
root@kali:~/vids/bash_scripting# ./test.sh192.168.0.104192.168.0.100192.168.0.103192.168.0.101
它显示所有阳性结果的时间通常不到一秒,并且可以扩展到更大的数字集.
那么,我的问题是,这是批处理脚本的限制吗?我发现很难相信 bash 在执行如此简单的任务时比 Windows CMD 解释器的性能好 100 倍?
如果这是有限的,PowerShell 是否提供了一种有竞争力的方式来创建任务?我在 foreach 循环中使用了 Start-Job
但这似乎对大量任务(即/16 网络上的 Test-Connection
)不可行.
编辑 1
@ECHO 关闭对于/L %%i 在 (100, 1, 110) DO (ping -n 1 192.168.0.%%i |找到时间=")
这会输出当前窗口并花费与初始变体一样长的时间
@ECHO 关闭对于/L %%i 在 (100, 1, 110) DO (开始 ping -n 1 192.168.0.%%i |找到时间=")
这会输出到每个 IP 的唯一窗口,并且需要同样长的时间才能完成.
异步并非易事,但借助 PowerShell,您可以轻松完成任务.
这段代码应该像你的 bash 代码那样运行,返回所有响应没有错误的主机的 IP 地址:
$IPAddresses = 100..110 |ForEach-Object { "192.168.0.$_";}$JobList = $IPAddresses |ForEach-Object {测试连接 -ComputerName $_ -Count 1 -AsJob;}Receive-Job -Job $JobList -AutoRemoveJob -Wait |`Where-Object { $_.StatusCode -eq 0 } |`Select-Object -ExpandProperty 地址;
以上对我来说在 3 秒内完成了大约 250 台主机.
Test-Connection
本身声称最多使用 32 个同时连接(可通过 -ThrottleLimit
设置进行配置)但它确实看起来像 -Delay
如果您的目标范围很广,则该选项会完全覆盖该设置.
您可能会遇到 PowerShell v4 及更早版本的问题,因为它的行为可能略有不同.Test-Connection
特别是在不同版本的 Windows 或 PowerShell 上似乎是特殊的.至少,我一直记得它抛出一个错误,而不是在以前的版本中返回错误状态代码.
如果您想对 ping 进行比 Test-Connection
提供的更细粒度的控制——例如,它默认为 1 秒超时,因此当任何主机处于down 是 1 秒——您可能必须恢复到直接调用 Get-WMIObject -Query "SELECT * FROM Win32_PingStatus WHERE Address = '$IP' AND TimeOut = 200" -AsJob
.>
I've currently got a very simple script which pings 10 IPs:
@ECHO OFF
for /L %%i in (100, 1, 110) DO (
START /W /B cmd /c ping -n 1 192.168.0.%%i | find "time="
)
The output is as expected:
C:Usersherpderp>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
However, it is VERY slow and definitely happening sequentially. When I run this with a PowerShell Measure-Command
I get these results:
PS C:Usersderpherp> 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
So we see it is taking ~23 seconds to execute.
Now, if I were on a Linux system and wanted to do this same thing, I can do the following:
#!/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
The result is different in a variety of ways:
The results aren't always sequential, meaning it actually started the commands more asynchronously:
root@kali:~/vids/bash_scripting# ./test.sh 192.168.0.104 192.168.0.100 192.168.0.103 192.168.0.101
The time for it to display all of the positive results is typically less than a second and this scales to much larger sets of numbers.
So, my question, is this a limitation of batch scripting? I find it hard to believe that bash performs literally 100s of times better than Windows CMD interpreter for such a simple task?
If this is limited, does PowerShell offer a competitive way to create tasks? I've used Start-Job
in a foreach loop but that seems to become unworkable for large numbers of tasks (ie Test-Connection
on a /16 network)
Edit 1
@ECHO OFF
for /L %%i in (100, 1, 110) DO (
ping -n 1 192.168.0.%%i | find "time="
)
This outputs the current window and takes just as long as the initial variant
@ECHO OFF
for /L %%i in (100, 1, 110) DO (
START ping -n 1 192.168.0.%%i | find "time="
)
This outputs to unique windows per IP and takes just as long to complete.
Asynchronous isn't easy, but with jobs you can get close pretty easily with PowerShell.
This code should behave how it looks like your bash code does, returning the IP address of all hosts that respond without error:
$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;
The above completes for me against ~250 hosts in 3 seconds.
Test-Connection
itself claims to use up to 32 simultaneous connections (configurable with -ThrottleLimit
setting) but it sure seems like the -Delay
option completely overrides that setting if you're targeting a wide range.
You may have issues with PowerShell v4 and earlier, as it may behave slightly differently. Test-Connection
in particular seems to be idiosyncratic on different versions of Windows or PowerShell. At least, I always remember it throwing an error instead of returning an error status code in previous versions.
If you want more fine grain control over the ping than Test-Connection
gives you -- for example, it defaults to a 1 second timeout so the minimum time you'll wait when any host is down is 1 second -- you'll probably have to revert to directly calling Get-WMIObject -Query "SELECT * FROM Win32_PingStatus WHERE Address = '$IP' AND TimeOut = 200" -AsJob
.
这篇关于使用 start 子命令改进循环的批处理文件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!