Wil*_*ibi 4 bash text-processing head wc
例如,我们有 N 个文件(file1, file2, file3 ...)
我们需要前 20%,结果目录应该是 (file1_20, file2_20, file3_20 ...)。
我在想wc用来获取文件的行,然后乘以 0.2
然后使用head20% 然后重定向到一个新文件,但我不知道如何自动化它。
所以创建一个单一的例子来工作:
root@crunchbang-ibm3:~# echo {0..100} > file1
root@crunchbang-ibm3:~# cat file1
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
Run Code Online (Sandbox Code Playgroud)
我们可以使用以下命令以字节为单位获取文件的大小stat:
root@crunchbang-ibm3:~# stat --printf %s "file1"
294
Run Code Online (Sandbox Code Playgroud)
然后使用bc我们可以将大小乘以 0.2
root@crunchbang-ibm3:~# echo "294*.2" | bc
58.8
Run Code Online (Sandbox Code Playgroud)
但是我们得到了一个浮点数,所以让我们将它转换为一个整数head(dd也可能在这里工作):
root@crunchbang-ibm3:~# printf %.0f "58.8"
59
Run Code Online (Sandbox Code Playgroud)
最后是文件 1 的前 20%(给予或接受一个字节):
root@crunchbang-ibm3:~# head -c "59" "file1"
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
Run Code Online (Sandbox Code Playgroud)
把它放在一起我们可以做这样的事情
mkdir -p a_new_directory
for f in file*; do
file_size=$(stat --printf %s "$f")
percent_size_as_float=$(echo "$file_size*.2" | bc)
float_to_int=$(printf %.0f "$percent_size_as_float")
grab_twenty=$(head -c "$float_to_int" "$f")
new_fn=$(printf "%s_20" "$f") # new name file1_20
printf "$grab_twenty" > a_new_directory/$new_fn
done
Run Code Online (Sandbox Code Playgroud)
wheref是在运行 for 循环的目录中找到的匹配项的占位符file*
完成后:
root@crunchbang-ibm3:~# cat a_new_directory/file1_20
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
Run Code Online (Sandbox Code Playgroud)
要获取前大约 20% 的行,我们可以替换stat --printf %s "$f"为:
wc -l < "$f"
Run Code Online (Sandbox Code Playgroud)
由于我们正在使用printf并且bc我们可以有效地从 中取整.5,但是如果一个文件只有 1 或 2 行长,它就会错过它们。所以我们不仅要四舍五入,还要默认至少抓取 1 行。
党。我用解析tar档案的精心方法写了这个完整的答案- 这很酷。但我到了最后,我意识到根本没有必要。你所需要的只是sed一些 shell 数学:
set ./file[1-5];i=1 n=;eval "${n:=
} sed -n \"$(grep -c '.\|' "$@"|
sed 's|\(.*\):\(.*\)|\
$i,$(((\2/5)+(i+=\2)-\2))w \1|
')\" <<!$n"'$(cat "$@")'"$n!$n"
Run Code Online (Sandbox Code Playgroud)
grep -c在您使用的任何文件中都有计数行 - 我使用了 globed file[1-5]- 然后将计数交给sed它 - 在 shell 的帮助下 - 编写自己的脚本。cat通过 here-document 提供输入。这是因为我不确定如果sed打开并开始写入其中一个文件cat试图读取它会发生什么 - 而且我怀疑它在处理缓冲区方面会比管道好一点取决于大小 - 但我对那部分不太清楚。
这样就可以读取单个流中的所有文件并w相应地处理输出。一个小的设置时,只需要增加文件号码正确-因此grep和eval-没有什么可怕的。下面是一些set -x输出来显示它在做什么:
+ set ./file1 ./file2 ./file3 ./file4 ./file5
+ i=1 n=
+ + grep -c .\| ./file1 ./file2 ./file3 ./file4 ./file5
sed s|\(.*\):\(.*\)|\
$i,$(((\2/5)+(i+=\2)-\2))w \1|
+ eval
sed -n "
$i,$(((18400/5)+(i+=18400)-18400))w ./file1
$i,$(((18411/5)+(i+=18411)-18411))w ./file2
$i,$(((18415/5)+(i+=18415)-18415))w ./file3
$i,$(((18418/5)+(i+=18418)-18418))w ./file4
$i,$(((18421/5)+(i+=18421)-18421))w ./file5" <<!
$(cat "$@")
!
+ cat ./file1 ./file2 ./file3 ./file4 ./file5
+ sed -n
1,3681w ./file1
18401,22083w ./file2
36812,40495w ./file3
55227,58910w ./file4
73645,77329w ./file5
Run Code Online (Sandbox Code Playgroud)
正如你所看到的,这些行是根据每个文件在流中的位置来寻址的,并且在w它们被读取到各自的文件名时被写入。但重要的是,这不会尝试处理路径名中的任何不可移植字符 - 特别是,在这种情况下,路径名中的换行符是非启动符,因为sed write 命令在换行符上分隔文件名参数。如果需要的ln话,这种情况很容易解决。
我还应该提到,单个脚本中可以支持的write 文件描述符的数量是有限制的sed。该规范说:
[
sed需要]支持至少十个不同的w文件,与许多实现的历史实践相匹配。鼓励实现支持更多,但符合要求的应用程序不应超过此限制。
所以上面写的命令应该可以移植到任何 POSIX 系统,最多 10 个并发读/写文件。如果将此类内容合并到可能需要更多内容的已发布脚本或应用程序中,则在处理/tmp. 喜欢:
: & set '"" "" "" "" "" "" "" "" "" "" ';n='
' f=/tmp/$$$!'_$((i+=1))' MAXw=[num]
while eval "set '$1$1' $1;exec <<!$n\$(((i=0)+\$#))$n!$n
i=\$(sed \"$(IFS=\ ;printf "\nw $f%.0s" $1)\")"
[ "$(($#==i?(_i=i-1):(MAXw=_i)))" -lt "$MAXw" ]
do :;done; rm "/tmp/$$$!"*; unset _i i f n
Run Code Online (Sandbox Code Playgroud)
...这应该相当可移植地衡量sed该领域的能力。GNU在大约一秒钟内为我sed停止了 4093 个并发打开的w文件,但这可能是我系统的最大值,并且也可能受到影响ulimit。当它通过时 - 因为$i每次尝试检查双倍的值 -$_i分别为 2560 和$i5120。我默认在循环关闭时设置$MAXw为更安全的$_i上面 - 主要是因为我不确定所有seds 是否会正确设置它们的返回如果他们无法打开w文件 - 但读者可以随心所欲地使用它。
请注意, 的初始[num]值$MAXw应该是一个实际数字——不管你想要的最大w文件是什么——而不是字面意思[num]。
再次关于 here-document - 我认为它 - 或类似的东西 - 在这种情况下是个好主意。sed必须在读取时维护其写入描述符,因此它可能会使用相同的输入/输出名称做什么我不知道 - 但我认为当我们很容易获得替代方案时,这是一个值得采取的机会。
我的测试文件是这样生成的:
for n in 1 2 3 4 5
do : & seq -s "$(printf "%015s--$n--%015s\n\t")" "$!" >"file$n"
done
Run Code Online (Sandbox Code Playgroud)
...它从废弃进程 PID 中的内核中获取相当连续的伪随机数。文件内容特意设计为指示拆分中的不匹配。以下是样本集之前和之后的样子:
前:
for f in file[1-5]; do
nl -ba "$f" | sed -n '$p;$=;1,3p
'; done
1 1 --1--
2 2 --1--
3 3 --1--
3681 3681 --1--
3681
1 1 --2--
2 2 --2--
3 3 --2--
3683 3683 --2--
3683
1 1 --3--
2 2 --3--
3 3 --3--
3684 3684 --3--
3684
1 1 --4--
2 2 --4--
3 3 --4--
3684 3684 --4--
3684
1 1 --5--
2 2 --5--
3 3 --5--
3685 3685 --5--
3685
Run Code Online (Sandbox Code Playgroud)
如果格式看起来有点奇怪,这可能是因为seq没有-s在第一个输出行之前插入分隔符字符串。重要的是sed,seq和nl所有出现在的行号一致。反正...
后: ...
sed -n
1,737w ./file1
3682,4418w ./file2
7365,8101w ./file3
11049,11785w ./file4
14733,15470w ./file5
...
1 1 --1--
2 2 --1--
3 3 --1--
737 737 --1--
737
1 1 --2--
2 2 --2--
3 3 --2--
737 737 --2--
737
1 1 --3--
2 2 --3--
3 3 --3--
737 737 --3--
737
1 1 --4--
2 2 --4--
3 3 --4--
737 737 --4--
737
1 1 --5--
2 2 --5--
3 3 --5--
738 738 --5--
738
Run Code Online (Sandbox Code Playgroud)
就是这样 - 简单、高效和流式传输。