例如,我们有 N 个文件(file1, file2, file3 ...)
我们需要其中的前 20%,结果目录应该类似于 (file1_20, file2_20, file3_20 ...)。
我想用它wc
来获取文件的行数,然后乘以 0.2
然后使用head
获取 20%,然后重定向到一个新文件,但我不知道如何自动化它。
答案1
因此,创建一个可以使用的示例:
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
我们可以使用以下命令获取文件的大小(以字节为单位)stat
:
root@crunchbang-ibm3:~# stat --printf %s "file1"
294
然后使用bc
我们可以将大小乘以 0.2
root@crunchbang-ibm3:~# echo "294*.2" | bc
58.8
然而,我们得到了一个浮点数,所以让我们将它转换为整数head
(dd
这里也可以工作):
root@crunchbang-ibm3:~# printf %.0f "58.8"
59
最后是 file1 的前百分之二十(给出或取一个字节):
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
把它们放在一起我们就可以做这样的事情
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
其中f
是在运行 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
更新(获取 20% 的行):
要获取前大约 20% 的行,我们可以替换stat --printf %s "$f"
为:
wc -l < "$f"
由于我们正在使用printf
并且bc
我们可以有效地从 进行舍入.5
,但是如果文件只有 1 或 2 行长,它将错过它们。因此,我们不仅要向上舍入,而且要默认至少抓取 1 行。
答案2
当当。我用一种复杂的解析存档的方法写了整个大答案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"
在你通配的任何文件中都有grep -c
行数 - 我通配file[1-5]
- 并将计数交给sed
它,然后 - 在 shell 的一点帮助下 - 编写自己的脚本。cat
通过此处文档提供输入。这是因为我不确定如果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
正如您所看到的,这些行是根据每个文件在流中的位置来寻址的,并且w
在读取它们各自的文件名时被写入。但重要的是,这不会尝试处理路径名中的任何不可移植字符 - 特别是,在这种情况下,路径名中的换行符是不可能的,因为sed
w
rite 命令在换行符上分隔文件名参数。如果有必要的ln
话,如果您需要的话,这种情况很容易解决。
w
我还应该提到, rite 文件描述符的数量是有限制的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
...这应该相当可移植地衡量sed
该领域的能力。 GNU在大约一秒钟sed
内停止在 4093 个并发打开w
文件上,但这可能是我的系统的最大值,并且也可能受到影响ulimit
。当它完成时 - 因为$i
每次尝试都会检查 doubles 的值 -$_i
被留在 2560 和$i
5120。我默认在循环关闭时设置$MAXw
为$_i
上面更安全的值 - 主要是因为我不确定是否所有sed
s 都会正确设置它们的返回值如果他们无法打开w
文件 - 但读者可以用它做他们想做的事。
请注意, 的初始[num]
值$MAXw
应该是一个实际数字 - 无论您想要的最大w
文件数可能是多少 - 而不是字面上的数字[num]
。
再次关于此处文档 - 我认为它 - 或类似的东西 - 在这种情况下是一个好主意。sed
在读取时必须维护其写入描述符,因此我不知道它可能会用相同的输入/输出名称做什么 - 但我认为当我们如此容易获得替代方案时,这不是一个值得抓住的机会。
我的测试文件生成如下:
for n in 1 2 3 4 5
do : & seq -s "$(printf "%015s--$n--%015s\n\t")" "$!" >"file$n"
done
...它从废弃进程 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
如果格式看起来有点奇怪,这可能是因为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
这就是——简单、高效、流式传输。
答案3
使用您提到的工具 + find
:使用or
获取行或字节1的百分比,其中 由给出, 其中由或给出, 最后将输出写入相应的. head -n perc file
head -c perc file
perc
(( count / 5 ))
count
wc -l < file
wc -c < file
file_20
注意:/
运算符向下舍入到最接近的整数,因此任何file*
具有行/字节count < 5
(因此perc = 0
)的文件都将生成一个空file*_20
文件。
获取前 20% - 行:
mkdir some_dir_name
find . -maxdepth 1 -iname 'file*' -exec sh -c 'head -n $(( $(wc -l < "$0") / 5 )) "$0" > some_dir_name/"$0"_20' {} \;
获取前 20% - 字节:
mkdir some_dir_name
find . -maxdepth 1 -iname 'file*' -exec sh -c 'head -c $(( $(wc -c < "$0") / 5 )) "$0" > some_dir_name/"$0"_20' {} \;
1
请注意,根据文本布局,两种方法可能会产生明显不同的结果,例如对于 10 行文本示例:
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua.
Abstract
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi
ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum...
总行数的前 20% = 前 2 行:
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua.
总字节数的前 20% = 第一行(已截断):
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do