我有 25k 个字符。
我希望编写一个脚本来打印(printf
据我所知是最便携的)任意数量的字符;按顺序逐步执行它们。
说:
命令号
在哪里数字可以是 1-25000 之间的任意值,并获得该输出。
我不想将数据放在单独的文件中(最简单的解决方案?),并且我更愿意仅使用 POSIX shell 命令(以使脚本尽可能可移植:我知道 awk 或 perl 可以破解这个简单地出来)。
我应该将这些数据存储在变量中吗?或者printf
通过cut
命令 ( cut -c -$1
) 运行我的完整程序?或者还有另一种(更好的?)解决方案吗?为什么我会选择一个选项而不是另一个选项?
我还忽略了哪些其他问题/警告?
答案1
你考虑过这个dd
命令吗?它允许您跳过任意数量的字节,然后输出任意数量的字节。
dd if=infilename bs=1 skip=sk count=ct 2>/dev/null
dd
,输入文件名,块大小1,先跳过斯克输入文件的字节,然后复制CT字节到标准输出(或使用 指定文件
of=name
)。重定向错误消息以避免通常在末尾打印的状态消息。
答案2
出于可移植性和可靠性的原因,将大数据存储为变量可能不是一个好主意。对于非awk
POSIX 解决方案以便更容易移植,请使用sed
.
解释
对于大量数据,避免存储为变量。虽然Bash 本身没有限制,但操作系统可能会这样做
假设您说“它可以在我的操作系统上运行”。但,
- 不同的操作系统会有不同的限制
- 因此,如果您想最大限度地提高可移植性,为什么要冒着脚本在一个操作系统上运行而在另一个操作系统上崩溃的风险,仅仅因为它们有不同的限制呢?
- 因此,通过首先不存储在变量中来避免此问题
那么我们将它存储在一个文件中。具体来说,将字符串分解为一个字符(或您希望的任何最小单位),并分成单独的行。
然后,使用sed
:
- sed 不需要加载整个大文件,它是逐行工作的
sed
在 POSIX 规范中定义,以满足您non-AWK
但仍然是 POSIX 的要求
另外考虑一下使用文件的代码维护优势。更新文件中存储的行可能比在脚本中导航代码更容易。
例子
拥有数据,每行一个字符(或任何您想要“逐步执行”的最小单位),例如在文件中data.lst
:
a
b
c
d
e
让你script.sh
包含:
#!/bin/bash
stop_number="$1"
sed -n "1,${stop_number}p" data.lst
因此,您在命令提示符下测试它并看到:
$ ./script.sh 3
a
b
c
- 它用于
sed
打印1
一直到由 指定的数字的行$stop_number
。为了清楚起见,我们写出来$stop_number
而不是直接写$1
$stop_number
当然是通过位置参数获得的$1
,就是你想要输入的任意数字- 所以它成功地按照
data.lst
它出现在的序列中的 3 个字符data.lst
- 此时如果您输入的数字大于实际行数,则只会显示所有行。
- 目前
data.lst
只是在同一个目录中,script.sh
但如果你不想要这样,如果你实际上在其他地方有它,那么~/some/dir/data.lst
你只需要调整它来说~/some/dir/data.lst
因此,一旦获得实际数据,data.lst
您就可以自己测试此脚本。
答案3
借用汤姆的一点:
#!/bin/sh
skp(){ dd bs="$1" skip=1 count=0; } # direct seek to target
rd (){ dd bs="$1" skip=0 count=1; } # single read at target
tail=$(sed -ne'/^don/{=;q;}' <"$0") # skip script by line#
while [ 1 -gt "$#" ] && exit # exit when args exhausted
exec <&- <"$0" || exit # exec <"$0" each iteration
do head -n "$tail" >&3 # only consider the tail
case ${2+$1} in # test args
(*[1-9]*|-*[!0]*) # skp() when ${2++} && $1 != 0
skp "$1";esac 2>&3 # send stderr to dev/null
rd "${2-$1}" 2>&3 # else just rd() from head of offset
echo; shift ${2+"2"} # append a newline and shift args away
done 3>/dev/null # put your data below this
不要将其放在变量中 - 将其放入您的文件中。一个 25k 的变量对于 shell 来说处理起来并不有趣,并且你的文件可以是寻求的在一个单一的,几乎原子行动。因此,如果您想打印字节 23843 - 24843,您可以执行类似上面的操作,然后使用以下命令调用它:
myscript 23843 1000
...首先 ahead
将从文件描述符中的共享标准中删除脚本的所有行,以便将偏移量精确设置为 25k 字符串的头部,然后第一个dd
将寻找该偏移量〜23k in,第二个dd
将读出它。这是最简单的方法。 shell 是为了逐个字符地读取而设计的 -read
例如,一个典型的 shell 的内置函数会执行以下操作:一字节 read()
循环直到找到换行符 - 并且直到找到换行符为止才停止。dd
将为每个read
参数对执行一个操作。
我是这样测试的:
# after a copy to my clipboard
ddscr(){ sh /tmp/ddscr.sh "$@"; }
{ xsel; man man; } > /tmp/ddscr.sh
{ echo show the size; ls -l /tmp/ddscr*
echo read from the top; ddscr 80
echo from the middle; ddscr 15k 160
echo from the tail; ddscr 64k | tail -n5
}
show the size
-rw-r--r-- 1 mikeserv mikeserv 37564 Dec 13 11:27 /tmp/ddscr.sh
read from the top
MAN(1) Manual pager utils MAN
from the middle
lso use manconv(1) directly.
However, this option allows you to convert several manual pages to a
single encoding without having
from the tail
31st March 2001 - present day: Colin Watson <[email protected]> is now
developing and maintaining man-db.
2.7.5 2015-11-06 MAN(1)
...和...
ddscr 10k 10 20k 10 10250 10
fi
is option
le. If
答案4
如果你确实想将程序和数据包装在同一个文件中,最好的方法是使用perl
.我不知道为什么你认为它是不可移植的:它是你遇到的任何 Unix 发行版的标准(包括 Linux 和 OS X);你在 Windows 上找不到它的标准,但在那里也找不到bash
。
#!/usr/bin/env perl
print substr(<DATA>, $ARGV[0], $ARGV[1]), "\n";
__DATA__
Just add all your text after
the __DATA__ line... no fuss, no quoting,
no tricks
例如,假设您将其命名为selective_print并且想要打印从10开始的30个字符:
% selective_print 10 30