【问题标题】:Awk: How do you build a string variable with new lines?awk:如何用新行构建字符串变量?
【发布时间】:2017-05-24 08:04:49
【问题描述】:

我正在尝试执行以下操作:

ls -l 在文件夹上的结果:

-rw-rw-r--   1 root  root  100  May 23 09:45 filename1
-rw-rw-r--   1 root  root  200  May 23 09:45 filename2
-rw-rw-r--   1 root  root  500  May 23 09:46 filename3

现在我想通过 awk 来执行以下操作:

800 bytes, files:
filename1
filename2
filename3

到目前为止,我可以通过 awk 将字节数相加:

output=`ls -l /some/folder/ | awk 'START {total = 0}; {total += $5} END{print total}'`

这个简单的给出:800

现在我想开始构建输出字符串,所以我试图获取文件名列表(我认为是第 9 列),我正在尝试这样:

output=`ls -l /some/folder/ | awk 'START {total = 0; files=""}; {total += $5 files="\n" files $9} END{print total "files:" files}'`

echo $output 给出以下内容:

800 filename1 filename2 filename3

我希望它显示:

800
filename1
filename2
filename3

我不明白为什么这些行没有分成新行?

【问题讨论】:

  • 尝试双引号"$output"
  • 我建议使用:find . -maxdepth 1 -type f -printf "%s %f\n"
  • 哦来吧....说真的,-1?...这个问题是一个好问题的定义 - 它表明我已经做出了努力,它是一个具体的问题,我有最小示例代码和输出......是的,我肯定因为不知道自己在做什么而像两块短板一样厚,但这就是提出问题的重点......它真的让我不想再使用 SO 当“批评者”如此严厉。我不在乎这些点......这只是'为什么'这里有这么多人准备-1并投票关闭一切......无论如何 - 非常感谢这里的实际贡献者:)
  • @PS。是的,这行得通!谢谢...虽然我完全不明白为什么。如果您想将其作为答案,我会很乐意将其标记出来。
  • @Cyrus 我正在使用嵌入式 bash (busybox) 的精简版本,它似乎不支持您建议的“printf”部分:(

标签: linux bash awk busybox


【解决方案1】:

当您不在 shell 中引用变量时,空格(包括换行符)会被折叠,因此对您所做的事情的简单修复是使用 echo "$output"

也就是说,我建议您不要使用 ls -l 来获取您的文件名及其大小,因为该工具并非设计用于解析。当您有一个有趣的文件名时,任何基于列的方法都会中断。

使用 GNU stat 允许您获取文件大小并控制输出,使用空字节 \0 使名称可以安全解析:

stat --printf '%s\0%n\0' * | awk -v RS='\0' '
NR % 2 { total += $0; next } # add to total on odd lines, skip to next line
{ files[++n] = $0 }          # save file names on other (even) lines
END { print total, "bytes, files:"; for (i = 1; i <= n; ++i) print files[i] }'

如果您不能使用stat --printf,那么您可以使用stat -c,并希望没有人在文件名中添加换行符:

stat -c '%s %n' * | awk '{ total += $1; files[NR] = substr($0, length($1) + 2) } 
END { print total, "bytes, files:"; for (i = 1; i <= NR; ++i) print files[i] }'

第一个字段包含名称,该行的其余部分是文件名,因此substr 用于获取该部分。

作为参数传递给stat* 被shell 扩展为当前目录中的完整文件列表。您可以通过传递/path/to/dir/* 或首先将cding 传递到目标来获取另一个目录中的文件。你也可以使用循环,例如:

for dir in dir1 dir2 dir3; do
    ( cd "$dir" && stat -c '%s %n' * | awk '...')
done

这里我使用( subshell ) 作为循环每次迭代后返回原始目录的一种懒惰方式。

【讨论】:

  • 感谢您提出的解决方案+1的详细信息,我的busybox stat不支持--printf ...我将把它添加到我的问题的细节中。对我来说有点烦人,我不能使用这些功能。但是 $output 周围的引用效果很好:)
  • 没问题。我已经编辑添加了一个适用于 stat -c 的版本,我认为它得到了更广泛的支持。
  • 非常感谢! - 工作得很好:) ...我试图让这个命令适用于特定目录,但我似乎无法弄清楚如何做到这一点。所以我可以通过 cd'ing 到有问题的目录来解决它 - 但我想在 4-5 个不同的目录上运行它。你能做这样的事情:“stat &lt;specify dir path&gt; --printf....”吗?我尝试使用 -f,但我认为这是针对特定文件而不是文件夹。
  • @code_fodder 我已经更新了我的答案以显示一些选项。
【解决方案2】:
ls -l | awk 'NR > 1 {s+=$5; f=f"\n"$NF} END{print s,f}'

ls -l 的输出中的第一行被忽略 (NR &gt; 1)。所有行中的5th 字段(文件大小)都添加到变量s 中。文件名附加到变量f(由换行符分隔)。在END 块中,打印sf

示例:

AMD$ ls -l
total 12
-rw-r--r-- 1 root root 165 May 24 08:23 ff
-rw-r--r-- 1 root root 165 May 24 08:23 gg
-rw-r--r-- 1 root root 165 May 24 08:23 hh

AMD$ ls -l | awk 'NR > 1 {s+=$5; f=f"\n"$NF} END{print s,f}'
495
ff
gg
hh

如果您想将其保存到变量中并稍后打印出来:

var=$(ls -l | awk 'NR > 1 {s+=$5; f=f"\n"$NF} END{print s,f}')
echo "$var"

【讨论】:

  • 谢谢 - 这很好地解释了这一点:)+1(反对票不是来自我,也不应该出现在 IMO,因为这很好地回答了这个问题)。
【解决方案3】:

为了保留变量的结构,应该用双引号引起来。

例子:

多行变量:

x='hey
> there'

不加引号:

echo $x
hey there

双引号:

echo "$x"
hey
there

【讨论】:

    猜你喜欢
    • 2021-09-08
    • 2014-07-27
    • 2013-05-08
    • 1970-01-01
    • 2018-07-06
    • 1970-01-01
    • 1970-01-01
    • 2014-08-28
    相关资源
    最近更新 更多