Bash 命令根据我传递的参数读取一行 - 执行基于列的查找

Bash command to read a line based on the parameters I pass - perform column-based lookups

我有一个文件links.txt:

1 a.sh
3 b.sh
6 c.sh
4 d.sh

所以,如果我将 1,4 作为参数传递给另一个文件 (master.sh),a.sh 和 d.sh 应该存储在一个变量中。

这会循环遍历每个参数并在链接文件中对其进行 greps。结果通过管道传输到 cut 中,我们将分隔符指定为带有 -d 标志的 space 以及带有 -f 标志的字段编号 2。最后,它被附加到名为 files 的数组中。

links="links.txt"
files=()

for arg in $@; do
    files=("${files[@]}" `grep "^$arg" "$links" | cut -d" " -f2`)
done;

echo ${files[@]}

用法:

$ ./master.sh 1 4
a.sh d.sh

编辑: 正如 mklement0 所指出的,上面的解决方案每个参数读取一次文件。下面首先构建模式,然后只读取一次文件。

links="links.txt"
pattern="^\s"
for arg in ${@:2}; do
    pattern+="|^$arg\s"
done
files=$(grep -E "$pattern" "$links" | cut -d" " -f2)
echo ${files[@]}

用法:

$ ./master.sh 1 4
a.sh d.sh

sed '3!d' 将打印第 3 行,但不会打印以 3 开头的行。为此,您需要 sed '/^3 /!d'。问题是您不能将它们组合起来以获得更多行,因为这意味着 "Delete everything that doesn't start with a 3",这意味着所有其他行都将被遗漏。因此,请改用 sed -n '/^3 /p',即默认情况下不打印并告诉 sed 要打印哪些行,而不是要删除哪些行。

您可以遍历参数并从中创建一个打印行的 sed 脚本,然后 运行 sed 使用此输出:

#!/bin/bash
file=
shift

for id in "$@" ; do
    echo "/^$id /p"
done | sed -nf- "$file"

运行 作为 script.sh filename 3 4.

如果你想从输出中删除 id,你可以使用

cut -f2 -d' '

或者您可以修改生成的 sed 脚本来完成工作

echo "/^$id /s/.* //p"

即仅在替换成功时打印。

以下awk解决方案

  • 保留参数顺序;也就是说,结果反映了指定查找值的顺序(与查找值恰好在文件中出现的顺序相反)。

    • 如果这不重要(即,如果以 file 顺序输出结果是可以接受的),下面的 readarray 技术可以与这个 one-liner,它是 :
      的广义变体 grep -f <(printf "^%s\n" "$@") links.txt | cut -d' ' -f2-
  • 表现良好,因为输入文件只被读取一次;唯一的要求是所有 key-value 对作为一个整体放入内存(作为单个关联 Awk 数组(字典))。

  • 适用于没有嵌入空格.

    的任何查找值
    • 同样,假设 output 列值(包含示例输入中的 a.sh 等值)没有嵌入空格。 awk 不能很好地处理带引号的字段,因此需要做更多的工作。
#!/bin/bash

readarray -t files < <(
  awk -v idList="$*" '
    BEGIN { count=split(idList, idArr); for (i in idArr) idDict[idArr[i]]++ }
     in idDict { idDict[] =  }
    END { for (i=1; i<=count; ++i) print idDict[idArr[i]] }
  ' links.txt
)

# Print results.
printf '%s\n' "${files[@]}"
  • readarray -t files 逐行读取标准输入(<)到数组变量files.

    • 注意:readarray 需要 Bash v4+;在 Bash 3.x 上,例如在 macOS 上,将这部分替换为 IFS=$'\n' read -d '' -ra files
  • <(...) 是一个 Bash process substitution ,笼统地说,它呈现所附命令的输出,就好像它是 (self-deleting)临时文件。

    • 此技术允许 readarray 到 运行 在 当前 shell(相对于 subshell 如果使用了管道),这是 files 变量在脚本其余部分保持定义所必需的。
  • awk命令分解如下:

    • -v idList="$*" 将所有 command-line 参数的 space-separated 列表作为单个字符串传递给 Awk 变量 idList.

      • 请注意,这假设参数没有 嵌入 空格,这里确实是这种情况,标识符通常也是这种情况。
    • BEGIN { ... } 仅执行 一次,然后处理各个行:

      • split(idList, idArr) 将输入的 ID 列表按空格拆分成一个数组,并将结果存储在 idArr.

      • for (i in idArr) idDict[idArr[i]]++ } 然后将(概念上规则的)数组转换为 associative 数组 idDict(字典),其键是输入ID - 这使得以后可以通过 ID 进行高效查找,并且还允许为每个 ID 存储查找 result

    • in idDict { idDict[] = } 对每个输入行进行处理:

      • 模式 in idDict returns 如果该行的第一个白色 space-separated 字段(</code>)为真 - 例如,<code>6 - 在键中(in) 的关联数组 idDict,如果是,则执行关联的操作 ({...})。
      • Action { idDict[] = } 然后将第二个字段 (</code>) - 例如 <code>c.sh - 分配给键 </code> 的 <code>iDict 条目。
    • END { ... }执行一次处理完所有输入行后:

      • for (i=1; i<=count; ++i) print idDict[idArr[i]] 按顺序 遍历所有输入 ID 并打印每个 ID 的查找结果,即 value具有该 ID 的字典条目。

这是另一个使用 grep 和 cut 的例子:

#!/bin/bash
for line in $(grep "\|" links.txt|cut -d' ' -f2)
do
    echo $line
done

用法示例:

./master.sh 1 4
a.sh
d.sh

为什么不只存储值并随意调用它们:

items=()

while read -r num file
do
  items[num]="$file"
done<links.txt

for arg
do
  echo "${items[arg]}"
done

现在您可以随时使用项目数组:)