有没有简单的方法可以从文件中读取任意行?例如“读取第 12 行”或“从最后一行向后读取到第一行”或“读取第 1、3、5 行...”。
答案1
不,您不能读取任意行,但在大多数其他编程语言中您也不能这样做。您必须从头开始逐行读取。除了原语之外\read
,还有\readline
逐行读取的原语。您可以使用它将所有行保存在宏中并在之后使用这些:
\newread\myread
\newcount\linecnt
\openin\myread=file.dat
\@whilesw\unless\ifeof\myread\fi{%
\advance\linecnt by \@ne
\readline\myread t\expandafter o\csname line-\number\linecnt\endcsname
}
答案2
TeX 能够从文件中读取行,只要它们有匹配的括号即可。这是通过\openin
和实现的\read
。
\newread\myread
\openin\myread=file.dat
\read\myread to \command
现在\command
将扩展到第一行的内容,file.dat
随后的\read\myread to \command
将对下一行执行相同操作。可以使用 测试文件的结尾\ifeof\myread
,但应始终记住 TeX 会在每个文件后附加一个空行。\myread
使用 关闭文件后,可以重新使用符号名称\closein\myread
。
必须提到通过“shell-escape”调用外部实用程序的可能性,这可能会更快、更灵活。
更新
九年后是时候更新了。这是一组宏,用于从文件中输出选定的行,每行之间有一些标记。给出了一个表格的应用程序。
第一个参数是文件名,第二个参数是逗号分隔的范围列表。范围的开始和结束用/
; 分隔,如果索引为负数,则表示从末尾开始。请注意,范围始终设置为“向上”。因此,如果您指定,3/1
您将按此顺序获得从 1 到 3 的行。
但是,如果\readfromfile*
调用,行会反转。因此\readfromfile*{1/3}
将按此顺序打印从 3 到 1 的行。反转是在最后一刻完成的,因此请确保也按相反顺序指定范围。
尾随可选参数指定在行间插入什么,默认为无。
\begin{filecontents*}{\jobname.dat}
line 1
line 2
line 3
line 4
line 5
line 6
\end{filecontents*}
\begin{filecontents*}{\jobname.tab}
line 1 & line 1
line 2 & line 2
line 3 & line 3
line 4 & line 4
line 5 & line 5
line 6 & line 6
\end{filecontents*}
\documentclass[twocolumn]{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\readfromfile}{s m m +O{}}
{% #1 = * if reversing
% #2 = file name, #3 = list of lines, #4 = what to use between items
\IfBooleanTF { #1 }
{ \bool_set_true:N \l__malmedal_rff_reverse_bool }
{ \bool_set_false:N \l__malmedal_rff_reverse_bool }
\malmedal_rff_main:nnn { #2 } { #3 } { #4 }
}
\bool_new:N \l__malmedal_rff_reverse_bool
\ior_new:N \g__malmedal_rff_file_ior
\seq_new:N \l__malmedal_rff_items_seq
\seq_new:N \l__malmedal_rff_items_out_seq
\clist_new:N \l__malmedal_rff_lines_clist
\prg_generate_conditional_variant:Nnn \tl_if_empty:n { e } { p,T,F,TF }
\cs_new_protected:Nn \malmedal_rff_main:nnn
{
% clear the variables
\seq_clear:N \l__malmedal_rff_items_seq
\seq_clear:N \l__malmedal_rff_items_out_seq
\clist_clear:N \l__malmedal_rff_lines_clist
% read the file line by line and stores them in a sequence
\ior_open:Nn \g__malmedal_rff_file_ior { #1 }
\ior_map_inline:Nn \g__malmedal_rff_file_ior
{
\seq_put_right:Nx \l__malmedal_rff_items_seq { \tl_trim_spaces:n { ##1 } }
}
\ior_close:N \g__malmedal_rff_file_ior
% output the items we want
\__malmedal_rff_output:nn { #2 } { #3 }
}
\cs_new_protected:Nn \__malmedal_rff_output:nn
{
\clist_map_function:nN { #1 } \__malmedal_rff_additems:n
\clist_map_inline:Nn \l__malmedal_rff_lines_clist
{
\seq_put_right:Nx \l__malmedal_rff_items_out_seq { \seq_item:Nn \l__malmedal_rff_items_seq { ##1 } }
}
\bool_if:NT \l__malmedal_rff_reverse_bool
{
\seq_reverse:N \l__malmedal_rff_items_out_seq
}
\seq_use:Nn \l__malmedal_rff_items_out_seq { #2 }
}
\cs_new_protected:Nn \__malmedal_rff_additems:n
{
\seq_set_split:Nnn \l_tmpa_seq { / } { #1 }
\int_compare:nTF { \seq_count:N \l_tmpa_seq = 1 }
{
\clist_put_right:Nn \l__malmedal_rff_lines_clist { #1 }
}
{
\int_step_inline:nnn
{
\tl_if_empty:eTF { \seq_item:Nn \l_tmpa_seq { 1 } }
{ 1 }
{
\int_compare:nTF { \seq_item:Nn \l_tmpa_seq { 1 } < 0 }
{
\seq_count:N \l__malmedal_rff_items_seq + (\seq_item:Nn \l_tmpa_seq { 1 }) + 1
}
{ \seq_item:Nn \l_tmpa_seq { 1 } }
}
}
{
\tl_if_empty:eTF { \seq_item:Nn \l_tmpa_seq { 2 } }
{ \seq_count:N \l__malmedal_rff_items_seq } % all the way up
{
\int_compare:nTF { \seq_item:Nn \l_tmpa_seq { 2 } < 0 }
{
\seq_count:N \l__malmedal_rff_items_seq + (\seq_item:Nn \l_tmpa_seq { 2 }) + 1
}
{ \seq_item:Nn \l_tmpa_seq { 2 } }
}
}
{ \clist_put_right:Nn \l__malmedal_rff_lines_clist { ##1 } }
}
}
\ExplSyntaxOff
\begin{document}
All lines\par
\readfromfile{\jobname.dat}{/}[\par]
All lines\par
\readfromfile{\jobname.dat}{1/}[\par]
All lines\par
\readfromfile{\jobname.dat}{1/-1}[\par]
\bigskip
Lines 1 and 4 to end\par
\readfromfile{\jobname.dat}{1,4/}[\par]
\bigskip
Lines from 1 to penultimate\par
\readfromfile{\jobname.dat}{1/-2}[\par]
\bigskip
Lines from antepenultimate to last\par
\readfromfile{\jobname.dat}{-3/-1}[\par]
\newpage
Reverse\par
\readfromfile*{\jobname.dat}{/}[\par]
\bigskip
Last and penultimate lines in reverse\par
\readfromfile*{\jobname.dat}{1/2}[\par]
\bigskip
Table with select lines\par
\begin{tabular}{ll}
A & B \\
\hline
\readfromfile{\jobname.tab}{2/4}[\\]
\end{tabular}
\bigskip
Table with all lines in reverse\par
\begin{tabular}{ll}
A & B \\
\hline
\readfromfile*{\jobname.tab}{/}[\\]
\end{tabular}
\end{document}
答案3
您在这里询问的是“随机访问”文本文件的行,据我所知,任何文件系统都不支持此功能。大多数编程语言都有一个seek
函数,可以在文件中按给定的行数向前和向后“跳跃”。字节由于可以计算“跳转到位置 x”的字节偏移量,因此这在二进制文件或文本文件中非常有效,因为其中每一行的长度都相同。
不幸的是,大多数文本文件每行的字节数各不相同,因此如果不读取文件内容,就无法预测给定行的起始偏移量。为了解决这个问题,您需要以某种方式“索引”文件,以便可以按任意顺序定位行。我知道的两种方法是:
读取文件内容,将其拆分成行并将结果存储在数组中。然后数组索引提供对文件内容的随机访问。这适用于小文件。
对于大文件,您必须担心时间或内存不足。节省内存的一个常用技巧是扫描文件并将每行的字节偏移量存储在数组中,而不是行本身的内容。然后可以将这些偏移量输入
seek
到文件中以定位行。Perl 的领带::文件模块使用此方法以及一些其他技巧将大文件表示为数组。
假设我们有以下输入文件:
foo
bar
baz
bim
使用 TeX 会遇到两个问题:
您每次只能读取文件的一行——没有诸如
seek
可以让您跳过内容的方法之类的花招。如果没有包的帮助,就没有可以用作数组并具有索引查找或最大长度等操作的对象。
解决方案:使用 LuaTeX
这是最简单的方法,因为 Lua 表可以制作出很棒的数组对象:
\documentclass{minimal}
\def\readfile#1#2{%
% Read the contents of a file #1 to an array and store the array name in #2
\directlua{%
local input = io.open('#1', 'r')
#2 = {}
for line in input:lines() do
table.insert(#2, line)
end
input:close()
}%
}
\begin{document}
% Read the contents of io.in to foo
\readfile{io.in}{foo}
% Loop over foo any way you want. The Lua shortcut for table.getn(foo) is #foo,
% but the # character is also special to TeX which causes some confusion.
\directlua{%
texio.write_nl("Normal:")%
for i = 1, table.getn(foo) do texio.write_nl(foo[i]) end}
\directlua{%
texio.write_nl("Reversed:")%
for i = table.getn(foo), 1, -1 do texio.write_nl(foo[i]) end}
\directlua{%
texio.write_nl("Odd entries only:")%
for i = 1, table.getn(foo), 2 do texio.write_nl(foo[i]) end}
\end{document}
日志lualatex
输出为:
Normal:
foo
bar
baz
bim
Reversed:
bim
baz
bar
foo
Odd entries only:
foo
baz
LuaFILE
对象input
也具有seek
可用于实现高级技巧的方法。
答案4
我也对这个问题感兴趣,并且找到了一个使用该包的解决方案readarray
:
\begin{filecontents*}{file.dat}
1 row
2 row
3 row
4 row
5 row
6 row
7 row
8 row
9 row
10 row
\end{filecontents*}
\documentclass[12pt]{article}
\usepackage{readarray}[2016-11-07]
\newcounter{rand}
\newcounter{someline}
%\ReadLine{<file name>} read one random line from file <file name>
\def\RandLine#1{
\readrecordarray{#1}\data
\setcounter{rand}{\pdfuniformdeviate\dataROWS{}}
\ifnum\therand=0\stepcounter{rand}\fi
\data[\arabic{rand}]
}
%\ReadLine{<file name>}{<line number>} read one line with number <line number> from file <file name>
\def\ReadLine#1#2{
\readrecordarray{#1}\data
\setcounter{someline}{#2}
\data[\arabic{someline}]
}
%\ReadLine{<file name>}{<first line>}{<last line>} read lines from file <file name> from <first line> to <last line>
\def\ReadLines#1#2#3{
\readrecordarray{#1}\data
\setcounter{someline}{#2}
\newcount\i\i=#3
\advance\i by 1
\loop\ifnum\value{someline} < \i
\data[\arabic{someline}]\par
\stepcounter{someline}
\repeat
}
\begin{document}
\RandLine{file.dat}
\ReadLine{file.dat}{2}
=============================
\ReadLines{file.dat}{3}{6}
\end{document}