【问题标题】:read values of txt file from bash [duplicate]从bash读取txt文件的值[重复]
【发布时间】:2017-03-15 15:34:54
【问题描述】:

我正在尝试从文本文件中读取值。

我有 test1.txt,看起来像

sub1    1   2   3
sub8    4   5   6

我想在指定“sub1”时获得值“1 2 3”。

我得到的最接近的是:

subj="sub1"
grep "$subj" test1.txt

但答案是:

sub8    4   5   6

我读过 grep 会为您提供匹配的下一行,因此我尝试将文本文件更改为以下内容:

test2.txt 看起来像:

sub1    
1   2   3

sub8    
4   5   6

但是,当我输入时

grep "$subj" test2.txt

答案是:

sub1

这应该是超级简单的东西,但我尝试过 awk、seg、grep、egrep、cat,但没有一个工作...我还阅读了一些相关的帖子,但没有一个真正有帮助

【问题讨论】:

  • grep 不返回匹配后的下一行,它返回匹配的行本身。看起来您的原始 grep 结果没有返回预期值,但第二个肯定是。我建议您在针对 test1.txt 执行 grep 之前尝试回显变量内容,以确保将变量设置为您认为的值。
  • @tripleee,我不确定副本是否代表 OP 正在尝试的内容——他们基于误解退回到多行匹配,但他们第一次尝试的格式有键和检索值在同一行。
  • @CharlesDuffy 嗯,您的解释似乎与我的一致?虽然如果 OP 真的想在比赛结束后打印该行,我们也有一个副本; e. G。 stackoverflow.com/questions/17283567/…
  • 问题不在于命令,而在于 Apple 文本文件格式的格式。 while IFS=$' \t\n' read -r -d $'\r' key value1 value2 value3 || [[ $key || $value ]]; do #printf 'Saw key: %q and values: %q\n' "$key" "$values" >&2 if [[ $key = "$target" ]]; then echo "Found values: $value1" echo "Found values: $value2" echo "Found values: $value3" fi done <Desktop/ANE/test1.txt回复Found values: 1 Found values: 2 Found values: 3

标签: bash sed grep


【解决方案1】:

Awk 工作:awk '$1 == "'"$subj"'" { print $2, $3, $4 }' test1.txt

该命令为test1.txt 中的所有行输出字段二、三和四,其中第一个字段是$subj(即:名为subj 的变量的内容)。

【讨论】:

  • 使用-v 将参数传递给awk,而不是在代码中执行字符串替换——否则,值可以转义它们的引号并运行任意命令。
  • 例如,假设您的 subj 包含 "+system("rm -rf $HOME")+" - 您不希望将其评估为 awk 代码。
  • 这样的答案是:sub83
  • @Charles Duffy:你能解释一下我的代码在哪里失败,而-v var="$subj" 没有?
  • @Joane:你是怎么测试的?我得到1 2 3
【解决方案2】:

使用您的原始文本文件格式:

target=sub1
while IFS=$' \t\n' read -r key values; do
  if [[ $key = "$target" ]]; then
    echo "Found values: $values"
  fi
done <test1.txt

这不需要外部工具,只使用 bash 本身内置的功能。见BashFAQ #1


正如在 cmets 中调试过程中出现的那样,如果您有一个传统的 Apple 格式的文本文件(仅限 CR 换行符),那么您可能需要更多类似的内容:

target=sub1
while IFS=$' \t\n' read -r -d $'\r' key values || [[ $key ]]; do
  if [[ $key = "$target" ]]; then
    echo "Found values: $values"
  fi
done <test1.txt

或者,使用awk(用于标准 UNIX 文本文件):

target="sub1"
awk -v target="$target" '$1 == target { $1 = ""; print; }' <test1.txt

...或者,对于仅包含 CR 换行符的文件:

target="sub1"
tr '\r' '\n' <test1.txt | awk -v target="$target" '$1 == target { $1 = ""; print; }'

如果正在读取的文本文件很小,这个版本会更慢(因为awk,像任何其他外部工具一样,需要时间来启动);但如果它很大,则更快(因为awk 的操作在启动后比 bash 的内置函数快得多)。

【讨论】:

  • 第一个什么也没做……第二个似乎没有完成计算……
  • 您需要从输入文件重定向的第二个。把&lt;test1.txt放在最后。
  • @Joane,...第一个确实有效,如果输入文件格式确实是您在问题中给出的。您可以使用单线进行测试:target=sub1; while read -r key values; do if [[ $key = "$target" ]]; then printf 'Found values:\t%s\n' "$values"; fi; done &lt;&lt;&lt;$'sub1\t1\t2\t3\nsub8\t4\t5\t6\n'
  • @Joane, ...要诊断输入格式 不是 您认为的情况,我建议添加一个调试行,如 printf 'Saw key: %q and values: %q\n' "$key" "$values" &gt;&amp;2if 之前的循环内,它将打印以显示隐藏字符的明确格式读取的内容(因此,如果您的密钥确实是\rsub1 而不仅仅是sub1,那将会显示出来)。
  • 答案是:Saw key: $'sub1\t1\t2\t3\rsub8\t4\t5\t6' and values: ''
【解决方案3】:

grep "sub1" test1.txt | cut -c6-

grep -A 1 "sub1" test2.txt | tail -n 1

【讨论】:

  • 回答第一个sub8 4 5 6 到第二个:sub8 2 3
  • 如果我的两个提案的结果都不是1 2 3,那么您的test1.txttest2.txt 与您随问题提供的示例内容不同。
  • 我认为它与分隔符有关... test1.txt 有 2 行,每列制表符分开但是当我在这里复制时:sub1 1 2 3 sub8 4 5 6
  • 至少对于我提出的第二个选项,分隔符无关紧要。它所做的只是向您显示比赛之后的那一行。还有其他事情发生。
  • 即使我使用while IFS= read -r line; do printf '%s\n' "$line" done &lt; "$file" sub8 4 5 6
【解决方案4】:

您做得对,但似乎 test1.txt 的值有误。

使用grep foo 你会得到所有带有 foo 的行。使用grep -m1 foo 查找仅包含 foo 的第一行。

然后您可以使用cut -d" " -f2- 获取 foo 后面的所有值,同时用空格分隔。

最后命令看起来像这样......

$ subj="sub1"
$ grep -m1 "$subj" test1.txt | cut -d" " -f2-

但这并不能解释为什么你一开始就找不到 sub1。 您是否阅读了正确的文件?

【讨论】:

  • 回答仍然sub8 4 5 6
  • 错误不在命令行,我认为是文件本身,你使用的是什么系统?
  • Mac Sierra 10.12.3
  • 您更改了$IFS 还是需要更改$IFS 才能使用正确的语言?由 edfault utf8 使用,但 Apple 有自己的语言和地区偏好。我认为更改$IFS 或系统语言将解决问题。
  • 我是mac的初学者,你能详细说明一下怎么做吗?确实,类似的事情正在发生for i in 'cat test1.txt'; do echo "$i"; done回复sub8 4 5 6...但如果我写IFS=$'\n' for i in 'cat test1.txt'; do echo "$i"; done我仍然会得到相同的..
【解决方案5】:

有很多方法可以做到这一点(并且比我给你的答案更短/更有效),但我假设你是 bash 的初学者,因此我会给你一些简单的东西理解:

egrep "^$subj\>" file.txt | sed "s/^\S*\>\s*//"

egrep "^$subj\>" file.txt | sed "s/^[^[:blank:]]*\>[[:blank:]]*//"

第一部分,egrep,将在 file.txt 中搜索您的主题行首(这就是 ^ 符号在 grep 字符串中的作用)。它还在寻找整个单词(\&gt; 正在寻找单词边界的结尾——这样sub1 与文件中的sub12 不匹配。)请注意,您必须使用 egrep 来获取 @ 987654328@,因为 grep 默认不识别转义序列。找到行后,egrep 然后将其输出传递给sed,这将删除每行的第一个单词和尾随空格。同样,sed 命令中的^ 符号指定它应该只匹配行首。 \S* 告诉它尽可能多地读取非空白字符。然后\s* 告诉 sed 尽可能多地吞噬空格。 sed 然后将它匹配的所有内容替换为空,留下其他内容。

顺便说一句,堆栈溢出中有一个帮助页面,它告诉你如何格式化你的问题(我猜这就是你被否决的原因)。

------------- 编辑 ---------

正如所指出的,如果您使用的是 Mac 或类似设备,则必须在 sed 表达式中使用 [:alnum:] 而不是 \S[:blank:] 而不是 \s(因为这些是可移植的到所有平台)

【讨论】:

  • $(subj)subj 作为命令运行并替换其输出,而不是替换名为subj变量 的值
  • 另外,\S\s 是 PCRE;普通sed 仅支持 BRE(默认)或 ERE(带有一些标志来启用它)。
  • 对不起,我的坏(习惯做,..)修复。
  • 回答仍然sub8 4 5 6
  • 顺便说一句,它实际上是[[:blank:]][[:alnum:]]——尽管您可以使用[^[:blank:]] 来表示“任何空白字符”。这些序列仅在[] 输入的字符集定义内有效。
【解决方案6】:
awk '/sub1/{ print $2,$3,$4 }' file
1 2 3

会发生什么?在 regexp /sub1/ 之后打印以下三个字段。 有什么缺点吗?它会影响空间。

【讨论】:

  • 我以审稿人的身份写信,因为您的答案已被标记为质量。您能否考虑在您的答案中添加一些评论,以揭示其工作原理。
  • 我得到的答案... awk '/sub1/{ print $2,$3,$4 }' test1.txt sub83
  • 这是我不明白的现象。但是,如果您输入 awk '/sub1/{$1="";print $2,$3,$4}' 文件会发生什么?
  • 但我必须指定文件,对吧?否则我会得到:awk '/sub1/{ print $2,$3,$4 }' fileawk: can't open file file
  • 是的,你必须指定你的文件。
【解决方案7】:

Sed 也可以:sed -n -e 's/^'"$subj"' *//p' file1.txt

在删除匹配的单词和后面的空格后,它会在行首输出与$subj 匹配的所有行。如果使用 TAB,则应将空格替换为 [[:space:]]

【讨论】:

  • 它什么也没做...
  • @Joane:你是怎么测试的?
  • @Toby Speight:其他人也没有解释他们的解决方案,我认为这很简单。我添加了一个解释。
  • 可能值得注意的是,$subj 这里是一个正则表达式,而不是一个文字字符串。在一般情况下,您可能需要使用 Bash 参数替换来转换带有 subj="${subj//\\/\\\\}"; ${subj//\*/\\*}" 等行的正则表达式元字符。
  • @Toby Speight:如果我开始使用 BASH 的正则表达式替换来准备目标表达式,我不需要使用 sed:那么 BASH 也可以处理剩下的部分。
猜你喜欢
  • 1970-01-01
  • 2021-11-27
  • 1970-01-01
  • 2012-07-03
  • 1970-01-01
  • 1970-01-01
  • 2021-05-16
  • 2021-01-06
  • 1970-01-01
相关资源
最近更新 更多