Bash 将命令解析为二维数组

Bash parse command into 2d array

我想制作一个脚本,列出所有可用的 MIDI 设备并提示用户选择一个,然后为每个音符分配快捷方式。 我设法使用 aseqdump -l 获得了所有设备的列表。在我的例子中,输出:

 Port    Client name                      Port name
  0:0    System                           Timer
  0:1    System                           Announce
 14:0    Midi Through                     Midi Through Port-0
 20:0    UMC404HD 192k                    UMC404HD 192k MIDI 1
 28:0    Launchpad S                      Launchpad S MIDI 1

用我最少的技能,我制作了一个脚本,在每一行之前添加了一个数字,所以它看起来像这样:

    Port    Client name                      Port name
 1) 0:0    System                           Timer
 2) 0:1    System                           Announce
 3) 14:0    Midi Through                     Midi Through Port-0
 4) 20:0    UMC404HD 192k                    UMC404HD 192k MIDI 1
 5) 28:0    Launchpad S                      Launchpad S MIDI 1

然后提示用户根据左边的数字选择设备。所有的乐趣和游戏,但我不知道如何只读取设备名称。例如,如果用户输入“4”,我希望我的 mDevice 变量等于 "UMC404HD 192k",这样我就可以调用 aseqdump -p $(mDevice) 并监视它的 activity.

我尝试逐字阅读命令输出,但这似乎毫无用处,因为每个字段中的字数从 1 到 5 甚至更多不等。是否可以将此命令的输入解析为二维数组,其中一维将存储设备?例如,理想情况下我会

mDevicesArray[0] = { "0:0", "System, "Timer"} 
mDevicesArray[1] = { "0:1", "System", "Announce"}
...
mDevicesArray[4] = { "28:0", "Launchpad S", "Launchpad S MIDI 1"}

然后使用这个数组进一步处理设备。

bash 没有二维数组。只要执行速度不是什么大问题,您就可以使用关联数组来模拟一个:

$ cat ./tst.sh
#!/bin/env bash

declare -A mDevicesArray

mDevicesSet() {
    local rowNr="" colNr
    shift
    for (( colNr=1; colNr<=$#; colNr++ )); do
        mDevicesArray["${rowNr},${colNr}"]="${!colNr}"
    done
}

mDevicesSet 1 '0:0'  'System'        'Timer'
mDevicesSet 2 '0:1'  'System'        'Announce'
mDevicesSet 3 '14:0' 'Midi Through'  'Midi Through Port-0'
mDevicesSet 4 '20:0' 'UMC404HD 192k' 'UMC404HD 192k MIDI 1'
mDevicesSet 5 '28:0' 'Launchpad S'   'Launchpad S MIDI 1'

printf '%s\n' "${mDevicesArray[4,2]}"

$ ./tst.sh
UMC404HD 192k

否则还有其他各种解决方法,例如使用 eval 我不推荐,或者为了简单、健壮和高效,您可以只使用 mDevicesArray1[1]="0:0"; mDevicesArray2[1]="System"; mDevicesArray3[1]="Timer" 等等,然后简单地编写函数来访问数组,就好像它们是 2D 一样,例如:

$ cat tst.sh
#!/bin/bash

mDevicesSet() {
    local rowNr=""
    shift
    mDevicesArray1["$rowNr"]=""
    mDevicesArray2["$rowNr"]=""
    mDevicesArray3["$rowNr"]=""
}

mDevicesGet() {
    local rowNr= colNr= val
    case $colNr in
        1 ) val="${mDevicesArray1[$rowNr]}" ;;
        2 ) val="${mDevicesArray2[$rowNr]}" ;;
        3 ) val="${mDevicesArray3[$rowNr]}" ;;
    esac
    printf '%s\n' "$val"
}

mDevicesSet 1 '0:0'  'System'        'Timer'
mDevicesSet 2 '0:1'  'System'        'Announce'
mDevicesSet 3 '14:0' 'Midi Through'  'Midi Through Port-0'
mDevicesSet 4 '20:0' 'UMC404HD 192k' 'UMC404HD 192k MIDI 1'
mDevicesSet 5 '28:0' 'Launchpad S'   'Launchpad S MIDI 1'

printf '%s\n' "$(mDevicesGet 4 2)"

$ ./tst.sh
UMC404HD 192k

与使用关联数组相比,虽然执行速度更快,但索引数组方法的缺点是您有 hard-coded 列数 (3),并且每行必须具有相同的列数和很难扩展到 2 个以上的索引。

aseqdump 仅列出输入端口,列输出使其难以解析。

解析 aconnect 的输出会更容易,它每行只有一个客户端或端口,并使用分隔符:

$ aconnect -io
client 0: 'System' [type=kernel]
    0 'Timer           '
    1 'Announce        '
client 64: 'Rawmidi 0 - EMU10K1 MPU-401 (UART)' [type=kernel]
    0 'EMU10K1 MPU-401 (UART)'
client 65: 'Emu10k1 WaveTable' [type=kernel]
    0 'Emu10k1 Port 0  '
    1 'Emu10k1 Port 1  '
    2 'Emu10k1 Port 2  '
    3 'Emu10k1 Port 3  '
client 128: 'DMIDI' [type=user]
    0 'DMIDI - Receive: [ff:ff:ff:ff]'
    1 'DMIDI - Transmit [ff:ff:ff:ff]'
client 129: 'LinuxSampler' [type=user]
    0 'LinuxSampler    '

(如果您实际上只需要输入端口,请仅使用 -i。)