我正在 Linux 中编写一个小型 bash 脚本来控制 Pulseaudio 输入/输出。
我想要做的是将除一个之外的所有接收器输入路由到一个或另一个接收器。我不想路由的接收器输入是这个:
pi@raspberrypi:~/fonction $ pactl list sink-inputs
Sink Input #36062
Driver: protocol-native.c
Owner Module: 2
Client: 198
Sink: 2
Sample Specification: s16le 2ch 44100Hz
Channel Map: front-left,front-right
Format: pcm, format.sample_format = "\"s16le\"" format.rate = "44100" format.channels = "2" format.channel_map = "\"front-left,front-right\""
Corked: no
Mute: no
Volume: front-left: 65536 / 100% / 0.00 dB, front-right: 65536 / 100% / 0.00 dB
balance 0.00
Buffer Latency: 120000 usec
Sink Latency: 236349 usec
Resample method: n/a
Properties:
media.name = "ALSA Playback"
application.name = "ALSA plug-in [snapclient]"
native-protocol.peer = "UNIX socket client"
native-protocol.version = "32"
application.process.id = "539"
application.process.user = "snapclient"
application.process.host = "raspberrypi"
application.process.binary = "snapclient"
application.language = "C"
application.process.machine_id = "69e523231bb44f2e926a758a63cbb5b1"
module-stream-restore.id = "sink-input-by-application-name:ALSA plug-in [snapclient]"
要将接收器输入移动到另一个接收器,我需要使用此命令:
pactl move-sink-input <sink-input id> <sink id>
所以我必须解析第一个命令的结果来获取Sink Input #ID
(包含在第一行中)但我必须使用条件来检查module-stream-restore.id
(最后一行)是否真的是我想要路由的。
我需要类似这样的东西:
if [ <last line> = 'sink-input name I need' ]
then
pactl move-sink-input <id from first line> <my sink name>
fi
我只是不知道如何解析命令来获取这两种信息。
现在,我只能获取最后一行,或者只能获取#ID。
pactl list short sink-inputs|while read stream; do
streamId=$(echo $stream )
id=$(echo pactl list sink-inputs | grep module-stream-restore.id | grep -o '".*"' |sed 's/"//g')
if [ "$streamId" != 'sink-input-by-application-name:ALSA plug-in [snapclient]' ]
then
pactl move-sink-input "$streamId" Snapcast
fi
我该如何继续解析结果,pactl list sink-inputs
以便我可以在 Bash 脚本中该命令结果的每个“块”(又名每个接收输入)中读取两个元素,而不仅仅是一个?
答案1
问题在于do
循环中设置的变量不会从一次传递到下一次传递而保留。
简单的解决方案是将列表输出保存到临时文件,然后扫描两次:-
pactl list short sink-inputs >temp.lst
streamId=$(sed -n 's/^Sink Input #\(.*\)$/\1/p' <temp.lst)
id=$(sed -n 's/.*module-stream-restore.id = "\(.*\)"$/\1/p' <temp.lst)
rm temp.lst
现在,您已经设置了所需的两个变量。这不是一个非常优雅的解决方案,但它很简单,易于理解和维护。请注意,我已用来sed
执行这些grep
功能:有关其工作原理的指南,请参阅这里。
但是,您可以使用更复杂的解决方案来避免使用临时文件:-
Ids="$(pactl list short sink-inputs | \
sed -n -e 's/^Sink Input #\(.*\)$/\1/p' \
-e 's/.*module-stream-restore.id = "\(.*\)"$/\1/p')"
现在,您有两个变量Ids
,它们之间用换行符分隔:您可以使用以下方式拆分它们:-
streamId="${Ids%$'\n'*}"
Id="${Ids#*$'\n'}"
如果您也不喜欢临时变量,Id
那么您可以使用Ids
!
我已经通过复制您的列表输出并使用它而不是真实pactl
命令来测试这一点,因此它应该全部与实时命令一起使用。
答案2
这个概念:
pactl …
使用设置调用LC_ALL=C
以避免本地化输出(您显然不需要这个,但一般情况下人们需要)。用于
egrep
丢弃不相关的行。您希望获得如下所示的行对:Sink Input #12345 module-stream-restore.id = "whatever"
假设这些线是成对的,
read
则两两地使用适当的IFS
(#
第一行是成对的,=
第二行是成对的)来提取相关数据。对于不需要的部分使用虚拟变量。- 使用提取的数据。请注意,
IFS='='
对于一对中的第二行,将提取 之后的所有内容=
,即相邻的空格和双引号。这就是为什么在代码(下面)中我匹配' "sink-… [snapclient]"'
,而不仅仅是'sink-… [snapclient]'
。
代码:
LC_ALL=C pactl list sink-inputs |
egrep "^Sink Input #|module-stream-restore.id = " |
while IFS='#' read dummy id && IFS='=' read dummy restid; do
if [ "$restid" = ' "sink-input-by-application-name:ALSA plug-in [snapclient]"' ] ; then
printf 'Match for ID %s.\n' "$id"
# do something
else
printf 'No match for ID %s.\n' "$id"
# do something else
fi
done