【问题标题】:How to take specific elements from the output of command and then display in a table format?如何从命令输出中获取特定元素,然后以表格格式显示?
【发布时间】:2018-07-03 00:17:07
【问题描述】:

我倾向于经常使用 macos 命令diskutil info -all,它包含的信息比我通常想要的要多得多,它以一种列表类型的格式显示它,并且连接了 19 个硬盘驱动器,这意味着需要大量滚动.我试图从diskutil info -all 的输出中仅提取四位信息,然后将其格式化为一个四列的表。

我正在使用 grep 来获取我想要的确切信息:

diskutil info -all | grep -e "Device Identifier:" -e "Device Node:" -e "Volume Name:" -e "Volume UUID:"

结果如下:

   Device Identifier:        disk0
   Device Node:              /dev/disk0
   Volume Name:              Not applicable (no file system)
   Device Identifier:        disk1
   Device Node:              /dev/disk1
   Volume Name:              Not applicable (no file system)
   Device Identifier:        disk1s1
   Device Node:              /dev/disk1s1
   Volume Name:              EFI
   Device Identifier:        disk1s2
   Device Node:              /dev/disk1s2
   Volume Name:              Old-Timemachine-Lion
   Volume UUID:              D04D1888-ABD9-3480-9692-60ECA458C372
   Device Identifier:        disk1s3
   Device Node:              /dev/disk1s3
   Volume Name:              Backup-Lion
   Volume UUID:              8C737824-F790-324B-AC4C-6F398D6CE947
   Device Identifier:        disk1s4
   Device Node:              /dev/disk1s4
   Volume Name:              Old-Timemachine-Garnet
   Volume UUID:              DECBEC2C-ADFD-397E-88B6-B9CC962E00E4

我想去掉第一列,然后将第二列分成四列,每个参数一列。上面的 sn-p 输出如下所示:

   disk0      /dev/disk0        Not applicable (no file system)
   disk1      /dev/disk1        Not applicable (no file system)
   disk1s1    /dev/disk1s1      EFI
   disk1s2    /dev/disk1s2      Old-Timemachine-Lion                D04D1888-ABD9-3480-9692-60ECA458C372
   disk1s3    /dev/disk1s3      Backup-Lion                         8C737824-F790-324B-AC4C-6F398D6CE947
   disk1s4    /dev/disk1s4      Old-Timemachine-Garnet              ECBEC2C-ADFD-397E-88B6-B9CC962E00E4

我发现我可以使用 cut 删除第一列,并认为我应该能够使用 printf 和命令替换来格式化为列。类似的东西:

printf "%14s    %14s    %14s    %14s\n" "$(diskutil info -all | grep -e "Device Identifier:" -e "Device Node:" -e "Volume Name:" -e "Volume UUID:" | cut -c 30-70)"

它可以摆脱第一列但不格式化,就像 printf 不工作一样。输出如下:

disk0
/dev/disk0
Not applicable (no file system)
disk1
/dev/disk1
Not applicable (no file system)
disk1s1
/dev/disk1s1
EFI
disk1s2
/dev/disk1s2
Old-Timemachine-Lion
D04D1888-ABD9-3480-9692-60ECA458C372
disk1s3
/dev/disk1s3
Backup-Lion
8C737824-F790-324B-AC4C-6F398D6CE947
disk1s4
/dev/disk1s4
Old-Timemachine-Garnet
DECBEC2C-ADFD-397E-88B6-B9CC962E00E4

我不擅长使用 shell 脚本,所以我怀疑我的方法是最好的方法,但这里缺少什么和/或做错了什么?我想学习。

【问题讨论】:

  • diskutils 结果中,每行开始前是否有空格?或者它是在 SO 中格式化时引入的?
  • @sjsam - diskutil 在每行的开头放置三个空格,因此 awk 中的 sub(/^ */,"") 将提供足够的修剪。
  • @sjsam,ghoti 是正确的。 diskutil 在每行的开头插入了三个空格。我不是特别关心他们。也许有一个或两个或三个空间将结果从边缘移开更漂亮?

标签: bash macos awk terminal


【解决方案1】:

您可以在diskutil info -all | grep -e "Device Identifier:" -e "Device Node:" -e "Volume Name:" -e "Volume UUID:" 之后使用以下awk 命令

$ diskutil info -all | grep -e "Device Identifier:" -e "Device Node:" -e "Volume Name:" -e "Volume UUID:" | awk 'BEGIN{FS=":";}/Device Identifier/{print ""}{printf $2}END{printf "\n"};' | sed -e 's/ \{3,\}/@/g;' | column -s'@' -t
disk0    /dev/disk0    Not applicable (no file system)
disk1    /dev/disk1    Not applicable (no file system)
disk1s1  /dev/disk1s1  EFI
disk1s2  /dev/disk1s2  Old-Timemachine-Lion             D04D1888-ABD9-3480-9692-60ECA458C372
disk1s3  /dev/disk1s3  Backup-Lion                      8C737824-F790-324B-AC4C-6F398D6CE947
disk1s4  /dev/disk1s4  Old-Timemachine-Garnet           DECBEC2C-ADFD-397E-88B6-B9CC962E00E4

在哪里

  • BEGIN{FS=":";} 定义 awk 的字段分隔符 :
  • /Device Identifier/{print ""} 每次到达新的设备标识符时都会打印 EOL
  • {printf $2} 将使您的垂直磁盘属性水平转换
  • END{printf "\n"} 打印最后一个 EOL 字符
  • sed -e 's/ \{3,\}/@/g;| column -s'@ -t 将其通过管道传输到 sed 和 column 以正确重塑它,您将获得以下输出:

【讨论】:

  • 你注意到这里介绍的标签了吗?例如Not applicable ?
  • 感谢您的选择!是时候休息一下吃午饭了;-)(我已经编辑了帖子以更正)
  • 谢谢!似乎每个人都建议使用 awk,但没有人提供我所做的与 printf 不兼容的原因。我对此感到有些沮丧。但是,即使解决方案都是 awk 的变体,但您是唯一一个解释了您添加到我的原始命令尝试中的代码的作用。我非常感谢这一点,因为没有解释,代码解决了任务,但没有教我它是如何工作的,因为我还不知道 sed 或 awk。谢谢!
  • 您的 printf 解决方案不起作用的原因是 printf 对读取其输入“天真”。输入的每个“单词”按顺序与每个格式说明符匹配。 Printf 无法知道 Not applicable (no file system) (例如)意味着只转到 %14s 格式说明符之一,Not applicable... 被“拆分”,每个单词依次转到每个 %14s 。我试图用IFS="\t" 的制表符分隔输出来欺骗它,并像"Not applicable (no file system)" 一样引用它,但这没有用。 IHTH
【解决方案2】:

关注awk 可能对您有所帮助。

awk '
/Device Identifier/{
  if(val){
    print val};
  val=$NF
}
/Device Node:/{
  val=val OFS $NF
}
/Volume Name:/{
  sub(/Volume Name:* +/,"");
  val=val OFS $0
}
/Volume UUID:/{
  sub(/Volume UUID:* +/,"");
  val=val OFS $0
}
END{
  if(val){
    print val}
}
'   Input_file

输出如下。

disk0 /dev/disk0    Not applicable (no file system)
disk1 /dev/disk1    Not applicable (no file system)
disk1s1 /dev/disk1s1    EFI
disk1s2 /dev/disk1s2    Old-Timemachine-Lion    D04D1888-ABD9-3480-9692-60ECA458C372
disk1s3 /dev/disk1s3    Backup-Lion    8C737824-F790-324B-AC4C-6F398D6CE947
disk1s4 /dev/disk1s4    Old-Timemachine-Garnet    DECBEC2C-ADFD-397E-88B6-B9CC962E00E4

【讨论】:

  • 感谢您对此的帮助。你和@James 都是在 awk 中完成的。我真的需要学习awk。关于你正在做什么的细节真的会很有帮助,因为在这一点上,每个人都使用过的所有这些 awk 对我来说都很模糊。
  • @CKnight,很高兴它帮助了你。不要担心,一旦你开始学习它会没事的。我们都在这里日复一日地学习,干杯:)
  • 嗨,我正在再次查看您的代码,因为@James 在评论中所说的左边是模式,右边是 awk 代码来做某事。我推测“正确”实际上是大括号中的内容。但是在底部的代码中,您会说“Input_file”。这究竟是什么?这是来自 diskutil 的管道输入结果吗?
【解决方案3】:

我的建议是使用 awk。这正是它的目的。这是一个时髦的工具,但一旦你学会了它,它就可以用于各种翻译/制表任务。

以下是您的问题的快速说明:

#!/bin/awk

/Device Identifier:/ { device=$3 }
/Device Node:/ { node=$3 }
/Volume Name:/ { split($0,parts,/:/); part2=parts[2]; sub(/^[ \t]*/,"",part2); name=part2 }
/Volume UUID:/ { uuid=$3 }
/^\*+/ { printf("%-10s %-16s %-40s %s\n",device,node,name,uuid) }

把它放在一个文件中然后运行diskutil info -all | awk -f ~/Desktop/that_file.awk

这只是冰山一角。 awk 可以做决定,做数学等等。这是一个穷人的perl,但我发现我在使用它时不会爆炸。 ;)

【讨论】:

  • 哦,@ravindersingh13 对卷名的解析比我好得多。
  • 感谢您对此的帮助。你和@Ravindersingh13 都是在 awk 中完成的。我真的需要学习 awk,因为解决方案似乎更简单?详细说明你在做什么真的会很有帮助。
  • 很难用 500 个字符来描述 awk,但这里是:awk 程序是一系列模式/代码对。每个模式(左侧)再次匹配每个输入行。如果该行与模式匹配,则执行右侧的语句。它使用带有变量、函数、条件、循环等的类 shell 语言。
  • 我的程序很典型。每个模式都在输出中寻找一些东西。与该模式匹配的行将执行右侧的代码,该代码只是提取它正在查找的值并将其存储在变量中。 ($0 是整个输入行,$1 是第一个空格分隔的字段,$2 是第二个字段,依此类推。)方便地,disktuil 在每条记录之后吐出一行 '*****',所以我使用它模式转储最后一组值。远非完美,但简单且可能有用。
  • 我在 Google 上搜索并找到了一个 [awk 教程] (grymoire.com/Unix/Awk.html),我将在这个周末阅读。为了快速修复,我将@Allan 提供的内容变成了别名(经过一些转义)。我正在考虑将我在所有这些对我的问题的有用答案中看到的内容转换为带有表头的 bash 函数,也许还有一个可选参数以进一步减少输出,并且只获取其中包含 UUID 的行并学习一点 awk在做的时候。 :-) 非常感谢您花时间回来详细说明您的代码。真的很感激。
【解决方案4】:

在将输入传递给awk 之前,我使用sed 进行了一些预处理。我还用下划线替换了每个字段中的空格(希望这不是问题),以便与column -t

给出更一致的结果
diskutil info -all |
grep -e "Device Identifier:" -e "Device Node:" -e "Volume Name:" -e "Volume UUID:" |
sed -E 's/^[[:blank:]]*(Device Identifier:.*)$/\n\1/;
s/[[:blank:]]+/ /g;
s/^[[:blank:]]+//;s/: /:/' |
awk -v RS="" -v FS=":" '{
for(i=2;i<=NF;i+=2){
gsub(/ /,"_",$i);printf "%s ",$i
}
printf "\n"
}' | column -t

输出

disk0    /dev/disk0    Not_applicable_(no_file_system)
disk1    /dev/disk1    Not_applicable_(no_file_system)
disk1s1  /dev/disk1s1  EFI
disk1s2  /dev/disk1s2  Old-Timemachine-Lion             D04D1888-ABD9-3480-9692-60ECA458C372
disk1s3  /dev/disk1s3  Backup-Lion                      8C737824-F790-324B-AC4C-6F398D6CE947
disk1s4  /dev/disk1s4  Old-Timemachine-Garnet           DECBEC2C-ADFD-397E-88B6-B9CC962E00E4

【讨论】:

  • 感谢您对此的帮助。我不知道 sed 或 awk 所以我并不真正了解您的代码中发生的事情,这是不幸的。我确实需要学习这些明显强大的工具。下划线的添加并没有真正困扰我,但同时我不明白他们为什么有帮助。
  • @CKnight column -t 根据空格分隔符分隔字段。考虑如果单个字段中有空格会发生什么。这就是下划线有帮助的地方。很好,您计划在您的曲目中添加sedawk。它们确实是很棒的工具。 :-)
  • 再次感谢您。是的,这个周末计划学习一些 awk。 :-) 我还没有搜索到关于 sed 的教程,但它在我的清单上。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-14
  • 2023-01-13
  • 1970-01-01
  • 1970-01-01
  • 2014-04-28
  • 1970-01-01
相关资源
最近更新 更多