如何在 Linux Shell 中使用正则表达式从文件中提取 IP 地址?
- 2024-10-24 08:50:00
- admin 原创
- 52
问题描述:
如何在 Linux Shell 中通过正则表达式提取文本部分?假设我有一个文件,其中每一行都是一个 IP 地址,但位于不同的位置。使用常见的 unix 命令行工具提取这些 IP 地址的最简单方法是什么?
解决方案 1:
您可以使用grep将它们拉出来。
grep -o '[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}' file.txt
解决方案 2:
这里的大多数示例将匹配 999.999.999.999,但从技术上讲这不是一个有效的 IP 地址。
以下将仅匹配有效的 IP 地址(包括网络和广播地址)。
grep -E -o '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)' file.txt
如果想要查看匹配的整行,请省略 -o。
解决方案 3:
对于我来说,这在访问日志中运行良好。
cat access_log | egrep -o '([0-9]{1,3}.){3}[0-9]{1,3}'
让我们逐一分析一下。
[0-9]{1,3}
表示 [] 中提到的范围出现一到三次。在本例中是 0-9。因此它匹配 10 或 183 之类的模式。后面跟着一个“。”。我们需要对其进行转义,因为“。”是一个元字符,对 shell 来说有特殊含义。
所以现在我们面临的模式是‘123’、‘12’等等。
此模式重复了三次(带有“。”)。因此我们将其括在括号中。
([0-9]{1,3}.){3}
最后,该模式再次重复,但这次没有“。”。这就是为什么我们在第 3 步中将其单独保存。
[0-9]{1,3}
如果 ips 位于每行的开头(如我的情况),则使用:
egrep -o '^([0-9]{1,3}.){3}[0-9]{1,3}'
其中 '^' 是一个锚点,表示在行首进行搜索。
解决方案 4:
我通常从 grep 开始,以获得正确的正则表达式。
# [multiple failed attempts here]
grep '[0-9]*.[0-9]*.[0-9]*.[0-9]*' file # good?
grep -E '[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}' file # good enough
然后我会尝试将其转换为sed
以过滤掉该行的其余部分。(阅读完此帖子后,您和我不会再这样做了:我们将改用grep -o
)
sed -ne 's/.*([0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}).*//p # FAIL
那时我通常会sed
因为没有使用与其他人相同的正则表达式而感到恼火。所以我转向了perl
。
$ perl -nle '/[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}/ and print $&'
无论如何,了解 Perl 都是有益的。如果您安装了少量的 CPAN,您甚至可以花费很少的代价使其更加可靠:
$ perl -MRegexp::Common=net -nE '/$RE{net}{IPV4}/ and say $&' file(s)
解决方案 5:
您可以使用sed。但是如果您了解 perl,那么这可能会更容易,并且从长远来看更有用:
perl -n '/(d+.d+.d+.d+)/ && print "$1
"' < file
解决方案 6:
我写了一个小脚本来更好地查看我的日志文件,它没有什么特别的,但可能会帮助很多正在学习 perl 的人。它在提取 IP 地址后对其进行 DNS 查找。
解决方案 7:
你可以使用我制作的一些 shell 助手:
https ://github.com/philpraxis/ipextract
为了方便起见,将它们包括在这里:
#!/bin/sh
ipextract ()
{
egrep --only-matching -E '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)'
}
ipextractnet ()
{
egrep --only-matching -E '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/[[:digit:]]+'
}
ipextracttcp ()
{
egrep --only-matching -E '[[:digit:]]+/tcp'
}
ipextractudp ()
{
egrep --only-matching -E '[[:digit:]]+/udp'
}
ipextractsctp ()
{
egrep --only-matching -E '[[:digit:]]+/sctp'
}
ipextractfqdn ()
{
egrep --only-matching -E '[a-zA-Z0-9]+[a-zA-Z0-9-.]*.[a-zA-Z]{2,}'
}
从 shell 加载它/获取它(当存储在 ipextract 文件中时):
$.ipextract
使用它们:
$ ipextract < /etc/hosts
127.0.0.1
255.255.255.255
$
以下为一些实际使用示例:
ipextractfqdn < /var/log/snort/alert | sort -u
dmesg | ipextractudp
解决方案 8:
对于那些想要一个现成的解决方案来从 apache 日志中获取 IP 地址并列出 IP 地址访问过网站的次数的人来说,使用这一行:
grep -Eo '[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}' error.log | sort | uniq -c | sort -nr > occurences.txt
禁止黑客的好方法。接下来您可以:
删除访问次数少于 20 次的行
使用正则表达式剪切到单个空格,这样你就只剩下 IP 地址
使用正则表达式截取 IP 地址的最后 1-3 个数字,这样你就只剩下网络地址了
deny from
在每行开头添加一个空格将结果文件保存为 .htaccess
解决方案 9:
grep -E -o "([0-9]{1,3}[.]){3}[0-9]{1,3}"
解决方案 10:
我建议使用 perl。 (\d+.\d+.\d+.\d+) 应该可以解决问题。
编辑:为了使其更像一个完整的程序,您可以执行以下操作(未经测试):
#!/usr/bin/perl -w
use strict;
while (<>) {
if (/(d+.d+.d+.d+)/) {
print "$1
";
}
}
每行处理一个 IP。如果每行有多个 IP,则需要使用 /g 选项。man perlretut为您提供了有关正则表达式的更详细教程。
解决方案 11:
之前的所有答案都有一个或多个问题。可接受的答案允许使用 999.999.999.999 之类的 IP 号码。目前获得第二多支持的答案要求以 0 作为前缀,例如 127.000.000.001 或 008.008.008.008,而不是 127.0.0.1 或 8.8.8.8。Apama 几乎说对了,但该表达式要求 ipnumber 是行上的唯一内容,不允许有前导或尾随空格,也不能从行中间选择 IP。
我认为可以在http://www.regextester.com/22上找到正确的正则表达式
因此,如果您想从文件中提取所有 IP 地址,请使用:
grep -Eo "(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]).){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])" file.txt
如果您不想重复使用:
grep -Eo "(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]).){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])" file.txt | sort | uniq
如果此正则表达式中仍存在问题,请发表评论。很容易找到许多错误的正则表达式来解决这个问题,我希望这个正则表达式没有真正的问题。
解决方案 12:
这里的每个人都在使用非常复杂的正则表达式,但实际上理解 POSIX 的正则表达式将允许您使用grep
这样的小命令来打印 IP 地址。
grep -Eo "(([0-9]{1,3}).){3}([0-9]{1,3})"
(旁注)这不会忽略无效的 IP,但它非常简单。
解决方案 13:
我已经尝试了所有答案,但所有答案都存在一个或多个问题,我列出了其中的几个。
部分检测
123.456.789.111
为有效 IP有些无法检测
127.0.00.1
为有效 IP有些无法检测以零开头的 IP,例如
08.8.8.8
因此我在这里发布了一个适用于上述所有条件的正则表达式。
注意:我已经使用以下正则表达式提取了超过 200 万个 IP,没有任何问题。
(?:(?:1dd|2[0-5][0-5]|2[0-4]d|0?[1-9]d|0?0?d).){3}(?:1dd|2[0-5][0-5]|2[0-4]d|0?[1-9]d|0?0?d)
解决方案 14:
我写了一篇关于这个主题的博客文章:如何使用正则表达式从纯文本中提取 IPv4 和 IPv6 IP 地址。
本文详细介绍了最常见的 IP 模式,通常需要使用正则表达式从纯文本中提取和隔离这些模式。
本指南基于 CodVerter 的IP Extractor源代码工具,用于在必要时处理 IP 地址提取和检测。
如果您希望验证并捕获 IPv4 地址,此模式可以完成这项工作:
(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)[.]){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)
或者验证并捕获带前缀的 IPv4 地址(“斜线符号”):
(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)[.]){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?/[0-9]{1,2})
或者捕获子网掩码或通配符掩码:
(255|254|252|248|240|224|192|128|0)[.](255|254|252|248|240|224|192|128|0)[.](255|254|252|248|240|224|192|128|0)[.](255|254|252|248|240|224|192|128|0)
或者要过滤掉子网掩码地址,可以使用正则表达式负向预测来执行此操作:
((?!(255|254|252|248|240|224|192|128|0)[.](255|254|252|248|240|224|192|128|0)[.](255|254|252|248|240|224|192|128|0)[.](255|254|252|248|240|224|192|128|0)))(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)[.]){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)
对于IPv6验证,您可以转到我在此答案顶部添加的文章链接。
以下是捕获所有常见模式的示例(取自 CodVerter 的 IP Extractor 帮助示例):
如果您愿意,可以在此处测试 IPv4 正则表达式。
解决方案 15:
您也可以使用 awk。例如...
awk '{i=1; if (NF > 0) do {if ($i ~ /regexp/) print $i; i++;} while (i <= NF);}' file
可能需要清理。只是一个快速而粗略的回应,基本上展示了如何用 awk 来做。
解决方案 16:
上面的awk示例对我来说不起作用,我需要专门用awk来完成,所以我想出了这个方法:
$ awk '{match($0,/[0-9]{1,3}+.[0-9]{1,3}+.[0-9]{1,3}+.[0-9]{1,3}+/); ip = substr($0,RSTART,RLENGTH); print ip}' your_sample_file.log
如果你从其他地方获取数据,也可以使用管道。例如,ipconfig
我还意识到该方法匹配了无效的IP地址。
以下是仅匹配有效 IPv4 地址的扩展版本:
$ awk 'match($0, /(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/) {print substr($0, RSTART, RLENGTH)}' sample_file.log
希望它能帮助别人。
解决方案 17:
这是一种非常强力的解决方案,我还没有时间处理子网掩码之类的事情。
由于许多awk
变体缺少的反向引用regex
、正则表达式中的范围表示法{n,m}
、FPAT
的数组目标能力match()
,我必须尽力在这里模拟其中的一些功能。
它regex
本身非常基础,并且非常有意,因为通过第一层过滤器的每个候选者都将被输入到 ip4 验证函数中,以确保值在范围内。
此外,我使用第二个数组来处理重复的情况(尽管它只是在 ASCII 字符串意义上进行了重复数据删除 - 目前,前导零将在每个唯一的 ASCII 字符串表示中出现多次)。
我知道这是极其蛮力且不体面的解决方案——我只能用我拥有的柠檬制作出这么多的柠檬水。
echo "${bbbbbbbb}" \n \n | mawk 'function validIP4(_,__,___) {
__^=__=___=4;—-__
if(--___!=gsub("[.]","_",_)) {
return !___ }
++___
do {
if ((+_<-_)||(__<+_)||(--___<-___)) {
_="[|]"
break
} } while (sub("^[^_]+[_]","",_))
return _!="[|]"
} BEGIN { FS = RS = "^$"
__=(__= (__="[0]*([012]?[0-9])?[0-9][.]")__)__
sub("...$","",__)
} END {
gsub(/[^0-9.]+/,OFS)
gsub(__,"=&~")
gsub(/[~][^0-9.=~]+[=]/,"~=")
gsub(/^[^=~]+[=~]|[=~][^=~]+$/,"")
split($(_<_),___,"[=~]+")
for(_ in ___) {
if ( ! (____[__=___[_]]++)) {
if (validIP4(__)) {
print (__) } } } }' \n \n | gsort -t'.' -k 1,1n -k 2,2n -k 3,3n -k 4,4n \n | gcat -n \n | rs -t -c$'
' -C= 0 4 \n | column -s= -t \n | lgp3 5
1 00.69.84.243 76 23.108.43.3 151 79.127.56.148 226 172.241.192.165
2 00.71.110.228 77 23.108.43.19 152 80.48.119.28 227 172.245.220.154
3 00.105.215.18 78 23.108.43.55 153 80.76.60.2 228 175.196.182.58
4 00.123.2.171 79 23.108.43.94 154 80.244.229.102 229 176.74.9.62
5 00.123.228.2 80 23.108.43.120 155 81.8.52.78 230 176.214.97.55
6 00.201.223.164 81 23.108.43.208 156 83.166.241.233 231 177.128.44.131
7 01.51.106.70 82 23.108.43.244 157 85.25.4.28 232 177.129.53.114
8 01.144.14.232 83 23.108.75.98 158 85.25.91.156 233 178.88.185.2
9 01.148.85.50 84 23.108.75.164 159 85.25.91.161 234 180.180.171.123
10 01.174.10.170 85 23.225.64.59 160 85.25.117.171 235 180.183.15.198
11 02.64.120.219 86 36.37.177.186 161 85.25.150.32 236 180.250.153.129
12 02.68.128.214 87 36.94.161.219 162 85.25.201.22 237 181.36.230.242
13 02.129.196.242 88 37.48.82.87 163 85.195.104.71 238 181.191.141.43
14 02.134.127.15 89 37.144.180.52 164 85.208.211.163 239 182.253.186.140
15 03.28.246.130 90 41.65.236.56 165 85.209.149.130 240 185.24.233.208
16 03.73.194.2 91 41.65.251.86 166 88.119.195.35 241 185.61.152.137
17 03.80.77.1 92 41.79.65.241 167 91.107.15.221 242 185.74.7.51
18 03.81.77.194 93 41.161.92.138 168 91.188.246.246 243 185.93.205.236
19 03.97.200.52 94 41.164.68.42 169 93.184.8.74 244 185.138.114.113
20 3.120.173.144 95 41.164.68.194 170 94.16.15.100 245 186.3.85.131
21 03.134.97.233 96 41.205.24.155 171 94.75.76.3 246 186.5.117.82
22 03.148.72.192 97 43.255.113.232 172 94.228.204.229 247 186.46.168.42
23 03.150.113.147 98 45.5.68.18 173 95.181.150.121 248 186.96.50.39
24 03.159.46.18 99 45.5.68.25 174 95.181.151.105 249 186.154.211.106
25 03.162.181.132 100 45.43.63.230 175 110.74.200.177 250 186.167.48.138
26 03.177.45.7 101 45.67.212.99 176 112.163.123.242 251 186.202.176.153
27 03.177.45.10 102 45.67.230.13 177 113.161.59.136 252 186.233.186.60
28 03.177.45.11 103 45.71.203.110 178 115.87.196.88 253 186.251.71.193
29 03.217.169.100 104 45.87.249.80 179 116.212.155.229 254 187.217.54.84
30 03.232.215.194 105 45.122.233.76 180 117.54.114.101 255 188.94.225.177
31 04.208.138.14 106 45.131.213.170 181 117.54.114.102 256 188.95.89.81
32 04.244.75.205 107 45.158.158.29 182 117.54.114.103 257 188.133.153.143
33 5.39.189.39 108 45.179.193.70 183 119.82.241.21 258 188.138.89.50
34 05.149.219.201 109 45.183.142.126 184 120.72.20.225 259 188.138.90.226
35 5.149.219.201 110 45.184.103.68 185 121.1.41.162 260 188.166.218.243
36 5.189.229.42 111 45.184.155.7 186 123.31.30.100 261 190.128.225.115
37 07.151.182.247 112 45.189.113.63 187 125.25.33.241 262 190.217.7.73
38 07.154.221.245 113 45.189.117.237 188 125.25.206.28 263 190.217.19.243
39 07.244.242.103 114 45.192.141.247 189 133.242.146.103 264 192.3.219.94
40 08.177.248.47 115 45.229.32.190 190 137.74.93.21 265 192.99.38.64
41 08.177.248.213 116 45.250.65.15 191 137.184.57.245 266 192.140.42.83
42 08.177.248.217 117 46.99.146.232 192 139.5.151.182 267 192.155.107.59
43 8.210.83.33 118 46.243.220.70 193 139.59.233.24 268 192.254.104.201
44 8.213.128.19 119 46.246.80.6 194 139.255.58.212 269 194.5.193.183
45 8.213.128.30 120 47.74.114.83 195 140.238.19.26 270 194.114.128.149
46 8.213.128.41 121 47.88.79.154 196 151.106.13.221 271 194.233.67.98
47 8.213.128.106 122 47.91.44.217 197 151.106.18.126 272 194.233.69.41
48 8.213.128.123 123 47.243.75.115 198 152.26.229.67 273 194.233.73.103
49 8.213.128.131 124 47.254.28.2 199 152.32.143.109 274 194.233.73.104
50 8.213.128.149 125 49.156.47.162 200 153.122.106.94 275 194.233.73.105
51 8.213.128.152 126 50.195.227.153 201 153.122.107.129 276 194.233.73.107
52 8.213.128.158 127 50.235.149.74 202 154.85.35.235 277 194.233.73.109
53 8.213.128.171 128 50.250.56.129 203 154.95.36.182 278 194.233.88.38
54 8.213.128.172 129 51.68.199.120 204 154.236.162.59 279 195.80.49.3
55 8.213.128.202 130 51.77.141.29 205 154.236.168.179 280 195.80.49.4
56 8.213.128.214 131 51.81.32.81 206 154.236.177.101 281 195.80.49.5
57 8.213.129.23 132 51.159.3.223 207 154.236.179.226 282 195.80.49.6
58 8.213.129.36 133 51.178.182.23 208 157.100.26.69 283 195.80.49.7
59 8.213.129.51 134 54.80.246.241 209 159.65.69.186 284 195.80.49.253
60 8.213.129.57 135 61.9.48.169 210 159.65.133.175 285 195.80.49.254
61 8.213.129.243 136 61.9.53.157 211 159.203.13.121 286 195.158.30.232
62 8.214.41.50 137 62.75.219.49 212 160.16.242.164 287 197.149.247.82
63 8.218.213.95 138 62.75.229.77 213 161.22.34.142 288 197.243.20.178
64 09.200.156.102 139 62.78.84.159 214 164.132.137.241 289 198.46.200.70
65 13.237.147.45 140 62.138.8.42 215 167.71.207.46 290 198.229.231.13
66 20.47.108.204 141 62.204.35.69 216 167.86.81.208 291 212.112.113.178
67 20.113.24.12 142 63.161.104.189 217 167.249.180.42 292 212.154.234.46
68 23.19.7.136 143 66.29.154.103 218 168.205.100.36 293 212.174.44.87
69 23.19.10.93 144 66.29.154.105 219 169.57.1.85 294 213.32.75.44
70 23.81.127.253 145 69.163.252.140 220 170.81.35.26 295 213.230.69.193
71 23.105.78.193 146 76.118.227.8 221 170.83.60.19 296 213.230.71.230
72 23.105.78.252 147 77.83.86.65 222 170.155.5.235 297 213.230.90.106
73 23.105.86.52 148 77.83.87.217 223 171.233.151.214 298 221.159.192.122
74 23.108.42.228 149 77.104.97.3 224 172.241.156.1 299 222.158.197.138
75 23.108.42.238 150 77.236.243.125 225 172.241.192.104 300 222.252.23.5
解决方案 18:
前面的示例无法返回没有部分行的 IP 和 CDIR。这是我使用的:
egrep -o '([0-9]{1,3}.){3}[0-9]{1,3}[|/{1,1}[0-9]{1,3}' file.txt
它匹配以下行:
192.168.254.0/24#Comment
22.198.172.141 # Comment
othertext=21.35.142.193othertext
othertext=21.35.142.0/24othertext
19.38.57.0/24
49.117.44.100 (tab)
111.222.333.444(carriage)
11.22.33.44.othertext
18.68.53.99othertext=127.0.0.1 othertext 192.168.1.0/24othertext
按照用户 Sarel Botha 提供的正则表达式来添加对有效 IP 的检测,它将是:
grep -E -o '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)[|/{1,1}[0-9]{1,3}' file.txt
解决方案 19:
如果这些都不起作用,请尝试这个:strace -e connect -f -o save_it file_path
警告:它使用 wine 运行文件然后获取 ip 地址,所以要小心!只是保存了.save_it
中提到的结果,指向一个可执行文件。file path
`file_path`
解决方案 20:
cat ip_address.txt | grep '^[0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,3}[,].*$|^.*[,][0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,3}[,].*$|^.*[,][0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,3}$'
假设文件以逗号分隔,并且 IP 地址的位置位于开头、结尾和中间某处
第一个正则表达式在行首查找 IP 地址的精确匹配。或之后的第二个正则表达式在中间查找 IP 地址。我们以这样的方式匹配它,即后面的数字应该正好是 1 到 3 位数字。这样可以排除像 12345.12.34.1 这样的虚假 IP 地址。
第三个正则表达式查找行末的 IP 地址
解决方案 21:
我只想从目录中的任何文件中获取以“10”开头的 IP 地址:
grep -o -nr "[10]{2}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}" /var/www
解决方案 22:
如果没有给出特定的文件而您需要提取 IP 地址,那么我们需要递归地执行此操作。grep 命令 -> 搜索文本或文件以匹配给定的字符串并显示匹配的字符串。
grep -roE '[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}' | grep -oE '[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}'
-r
我们可以搜索整个目录树,即当前目录和各级子目录。它表示递归搜索。
-o
仅打印匹配的字符串
-E
使用扩展正则表达式
如果我们没有使用管道后的第二个 grep 命令,我们就会得到 IP 地址以及它所在的路径
解决方案 23:
对于centos6.3
ifconfig eth0 | grep 'inet addr' | awk '{print $2}' | awk 'BEGIN {FS=":"} {print $2}'
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件