为了最终放入 bash 脚本和/或 Makefile,我想要找到 Debian 系统上最后插入的 USB 驱动器的设备节点路径。
它很可能是类似的东西/dev/sdb
,但如果可能的话,我宁愿以编程方式找到设备源,而不是假设和硬编码它。
我可以看到使用的挂载点列表findmnt
,并在该列表中直观地找到 USB 驱动器,但似乎没有一种可靠的方法来进行搜索,findmnt
除非您确切知道 USB 驱动器的“目标”或“源”字段值(例如/media/user/Thumbdrive
),我们无法动态地知道:
findmnt --noheadings --output source --target /media/user/Thumbdrive
/dev/sdb1
我在网上看到的一些指南提到您可以搜索输出dmesg
以获取有关最后插入的 USB 设备的信息,并且这看起来很有希望(因为有一行sdb: sdb1
,并且条目是按启动以来的时间记录的),但是如何/什么来对这样的输出进行模式匹配,因为最明智的做法可能是在上下文之后查找条目,usb-storage
并且在我们到达sdX: X
条目之前可能会有不同数量的行(以及可能与其他不相关的内核消息交织在一起)?
[58972.861628] usb 1-1: new high-speed USB device number 12 using xhci_hcd
[58973.017906] usb 1-1: New USB device found, idVendor=0930, idProduct=1400, bcdDevice= 1.00
[58973.017912] usb 1-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[58973.017916] usb 1-1: Product: TOSHIBA USB DRV
[58973.017919] usb 1-1: Manufacturer: TOSHIBA
[58973.017921] usb 1-1: SerialNumber: 03A81B061C4C86
[58973.019758] usb-storage 1-1:1.0: USB Mass Storage device detected
[58973.020517] scsi host2: usb-storage 1-1:1.0
[58973.116352] [UFW BLOCK] IN=wlp1s0 OUT= MAC=...
[58974.118040] [UFW BLOCK] IN=wlp1s0 OUT= MAC=...
[58975.160865] scsi 2:0:0:0: Direct-Access TOSHIBA TOSHIBA USB DRV PMAP PQ: 0 ANSI: 4
[58975.161608] sd 2:0:0:0: Attached scsi generic sg1 type 0
[58975.162908] sd 2:0:0:0: [sdb] 60555264 512-byte logical blocks: (31.0 GB/28.9 GiB)
[58975.163588] sd 2:0:0:0: [sdb] Write Protect is off
[58975.163598] sd 2:0:0:0: [sdb] Mode Sense: 23 00 00 00
[58975.164252] sd 2:0:0:0: [sdb] No Caching mode page found
[58975.164264] sd 2:0:0:0: [sdb] Assuming drive cache: write through
[58975.181161] sdb: sdb1
[58975.196147] sd 2:0:0:0: [sdb] Attached SCSI removable disk
[58976.576602] [UFW BLOCK] IN=wlp1s0 OUT= MAC=...
例如,这条awk
线是一个开始,但它并不强大。
dmesg -t | awk '/^\s+sd\w:\s.*/ {print "/dev/"$2}' | tail -1
/dev/sdb1
有什么想法吗?或者更好的是,是否有专门用于这项工作的工具(最好是默认与 Debian 捆绑在一起的工具)我还没有发现?
谢谢!
更新
为了更清楚起见,我正在寻找一种可以在 bash 脚本或 Makefile 中调用的解决方案(并且很可能事先不需要超级用户访问权限),并且假定已插入磁盘前脚本运行,因此需要事先创建文件来监视内核信息的解决方案可能并不可取。
此外,如果在脚本运行之前插入并移除了驱动器,那么在最终运行时也需要考虑这个问题 - 即最好的解决方案是知道当前系统状态(或至少一个尽可能接近当前状态)。
bash 脚本/Makefile 试图让用户更轻松地将磁盘映像写入 USB 驱动器(通过猜测 USB 驱动器可能是什么并提出建议),因此尝试减少误报等将是一个考虑因素。
答案1
初步说明
这个答案是在 OP 添加以下内容之前写的:
我正在寻找一种可以在 bash 脚本或 Makefile 中调用的解决方案(并且很可能事先不需要超级用户访问权限),并且假定在脚本运行之前已经插入了磁盘,因此需要创建文件来监视内核信息的解决方案在这里不起作用。
要使答案起作用,您需要创建一个具有特定内容的文件。这需要 root 访问权限,但创建文件是一度行动。该文件在重启后仍有效。创建该文件后,解决方案将生效。
基本解决方案
/etc/udev/rules.d/91-usb-info.rules
使用以下内容创建:
SUBSYSTEM=="block", ENV{DEVTYPE}=="disk", SUBSYSTEMS=="usb", ACTION=="add", RUN+="/bin/sh -c 'echo \"$name\" >/dev/shm/usb-info'"
这会将文件写入(而不是附加到)/dev/shm/usb-info
。该文件最多会保存一个名称,即最后插入的 USB 驱动器的名称。这意味着您无需解析或过滤文件,只需读取它即可。
/dev/shm
是临时文件系统,tmpfs
,则在重启后不会继续存在。如果文件不存在,则表明尚未有设备触发该规则。
至少存在两个问题:
重定向会
>
先截断文件,然后再写入。当文件为空时,可能会发生读取文件的情况。处理此问题的标准方法是通过写入同一文件系统上的另一个文件来更新文件,然后自动将 (mv
) 移动到所需名称(我相信mv
在tmpfs
是原子的)。如果设备被移除,则其名称惯于将从文件中删除。请注意,从技术上讲,它仍然是“最后插入的 USB 驱动器”的名称,即使驱动器不再存在。所以这是你明确要求的;但我不确定这是否是你真正想要的。
不太基本的解决方案
使用这些来代替(而不是与)上述基本解决方案:
SUBSYSTEM=="block", ENV{DEVTYPE}=="disk", SUBSYSTEMS=="usb", ACTION=="add", RUN+="/bin/sh -c 'echo \"$name\" >>/dev/shm/usb-info'"
SUBSYSTEM=="block", ENV{DEVTYPE}=="disk", SUBSYSTEMS=="usb", ACTION=="remove", RUN+="/usr/bin/sed -i \"/^$name$/d\" /dev/shm/usb-info"
现在/dev/shm/usb-info
是一个以换行符结尾的名称列表。使用 获取最后一个tail -n 1 /dev/shm/usb-info 2>/dev/null
。空结果意味着“尚未连接任何设备”或“之前连接的所有设备都已断开连接”;无论如何:“没有合适的设备可用”。
该解决方案并不完美。有几个问题:
$name
嵌入在sh
/sed
代码中。一个不合适的名字可能会破坏代码(比较嵌入{}
,类似的故事)。sdb
不过像 这样的姓氏是幸运的。一个不幸的名字可能行为不当
echo
. 像 这样的姓氏sdb
是吉祥的。我们期望用 附加一个短字符串
echo … >> …
使用单个系统调用,因此它是原子的。我的意思是tail -n 1 /dev/shm/usb-info
永远不会给你一行尚未完全写入的内容;它会在 之前echo
或之后看到文件echo
,但不会在 期间看到echo
。一般来说,如果你想写一个更长的字符串,那么你应该使用sed -i
这里也是如此,或者类似的cp /dev/shm/usb-info /dev/shm/elsewhere && echo … >> /dev/shm/elsewhere && mv /dev/shm/elsewhere /dev/shm/usb-info
。如果在几秒钟内有许多 USB 设备连接/断开连接,则可能会发生许多规则实例尝试
/dev/shm/usb-info
同时处理的情况。为了避免可能导致文件错过某些设备(或错过它们的缺失)的竞争条件,我们应该确保规则一次运行一个。可能性:我不是 udev 专家,但我想这
udevadm control -m 1
可能是最简单的解决方案。请参阅man 8 udevadm
。或者我们在里面运行
RUN+
应该使用flock
。提示:不要使用/dev/shm/usb-info
作为 的目标文件flock
,因为sed -i
会用具有另一个 inode 的文件替换该文件,这将使锁定无效。专门为 创建一个单独的文件flock
。
不过,在大多数情况下,我给你的解决方案应该表现良好。现在你知道了一些潜在的缺陷,所以如果你认为有必要的话,可以改进这个解决方案。我不会在这里创建一个完全可靠的解决方案。
如果您决定改进解决方案,请考虑将代码放在单独的脚本中(例如/usr/local/bin/usb_info_helper
)并保持 udev 规则简单(例如RUN+="/usr/local/bin/usb_info_helper add $name"
)。重点是改进的代码不会是微不足道的。将其放在单独的文件中可以让您保持其整洁、多行、正确缩进;此外,正确引用也会更容易。
答案2
使用lsblk
一些模式匹配和字母排序可能是一种临时方法:
lsblk --noheadings --raw --output rm,tran,type,path --sort path | awk '/^1 usb disk/ {d=$4} END {print d}'
和:
--noheadings
删除列输出标题--raw
删除列格式--output rm,tran,type,path
仅返回列列表:rm
可移动设备标志(0 或 1)tran
设备传输类型(usb、sata 等)type
设备类型(磁盘、分区等)path
设备节点的路径(例如 /dev/sda)
--sort path
按“路径”列的字母顺序对输出列表进行排序(即 sda sdb sdc)
然后将该输出传输到awk
匹配:
1
带有可移动媒体标志的行,- 与“tran”列匹配
usb
,并且是disk
“type” - 然后将最后一列(即路径)捕获到
$4
- 遍历
END
列表,然后print
这样我们就对“最后安装的”可移动驱动器有了最好的猜测。
优点
- 使用常用工具的一行程序,无需
sudo
。
缺点
- 它依赖于字母排序,因此无法可靠地匹配已断开/重新连接的设备,且不按字母顺序排列即,它假设当前机器状态是最后一个 USB 驱动器被赋予按字母顺序排列的设备节点路径的状态。例如,如果 USB
1
插入在/dev/sda
并且 USB2
在/dev/sdb
并且 USB1
被移除并重新插入(并且系统将其分配给/dev/sda
因为该节点路径现在再次空闲),则此模式将匹配 USB2
在/dev/sdb
即使从技术上讲,USB1
是“最后插入的 USB 驱动器”。
答案3
构建于这个答案,对于脚本来说,更强大的解决方案可能是fdisk
使用以下方式进行模式匹配awk
:
last_disk="$(sudo fdisk --list | awk -F '[ :]' '$0 ~ "^Disk /dev/" {path=$2} END {print path}')"
将为您提供“最后一个磁盘”的设备节点路径,然后您可以测试lsblk
它是否是 USB 设备:
if [[ -z "${last_disk}" ]] || \
! lsblk --raw --noheadings --output tran,type,rm "${last_disk}" | grep --basic-regexp --quiet '^usb disk 1'; then
>&2 echo "No usable device found. Please insert a USB disk and try again."
exit 2
fi
在哪里:
--output tran,type,rm
仅返回列列表:tran
设备传输类型(usb、sata 等)type
设备类型(磁盘、分区等)rm
可移动设备标志(0 或 1)
因此grep
可以做以下工作:
- 如果某一行以传输类型“usb”开头,且为“磁盘”,并且可移动设备标志设置为“1”,则返回成功
如果一切成功,设备节点路径${last_disk}
就是我们最后插入的 USB 设备。