65.9K
CodeProject 正在变化。 阅读更多。
Home

PsCal - 创建个性化 PDF 日历

starIconstarIconstarIconstarIconstarIcon

5.00/5 (5投票s)

2023年1月10日

CPOL

21分钟阅读

viewsIcon

6402

本文描述了一组批处理、AWK 和 PostScript 文件,它们共同允许您为某一年创建个性化的 12 页 PDF 日历。

引言

本文介绍如何以多页 PDF 文件的形式创建各种年份的日历。一个可选的简单文本文件允许您个性化您创建的任何日历。

以下是 PsCal 创建的日历中一个月份的示例

June 2020

这些日历由我称之为 PsCal 的一组文件创建。PsCal 使用两种**古老**的编程语言生成日历:AWK 和 PostScript。

关于 AWK

AWK 于 1970 年代在贝尔实验室开发,至今仍在广泛使用。它是一种旨在让您快速处理文本文件以从中提取信息的工具。AWK 的 GNU 版本 GAWK 由 Arnold Robbins 维护良好(并且已维护多年)。GAWK 是 PsCal 使用的 AWK 版本。

在 PsCal 压缩文件中,我提供了一个名为 * _awkinfo.txt* 的文件,供不熟悉 AWK 的任何人阅读。它包含大量有关 AWK 的信息,应该可以帮助您理解 PsCal 中包含的 AWK 代码。我希望即使是经验丰富的 AWK 用户也能在那里找到一些新的、有点有用的信息。

关于 PostScript

PostScript 于 1980 年代中期由 Adobe Systems 推出。它提供了一种描述打印页面的方法,这种方法不依赖于用于显示页面的设备(除了它能识别 PostScript 语言这一事实)。

在 PsCal 压缩文件中,我提供了另一个文件 * _psinfo.txt*,供不熟悉但对 PostScript 语言感到好奇的任何人阅读。

AWK 和 PostScript 还有用吗?

虽然 AWK 语言仍然非常活跃和良好,但 PostScript 并非如此。也就是说,Adobe 不再开发 PostScript;它已用 PDF 格式取代了 PostScript。然而,粗略查看 PDF 1.7 规范,会发现 PDF 格式深受 PostScript 的影响。从这个角度来看,一点 PostScript 知识作为学习 PDF 的前身会很有帮助。

尽管 PostScript 是为描述打印页面而开发的,但它是一种通用语言。您可以使用它来完成各种项目。其中一些更有趣的项目涉及制作让人联想到您可以使用 Spirograph 玩具制作的图画。以下是从 * SpiralSquares.ps* 程序创建的示例 PDF 的一部分,您可以在 PsCal 压缩文件的 * postscript\examples* 目录中找到它

Square Spirals

尽管乍一看似乎并非如此,但上面的示例完全由正方形组成!

PsCal 的起源

早在 1993 年我在德州仪器工作时,我发现了一个 UNIX 脚本,它可以为一个月份创建一个漂亮的 PostScript 日历。该脚本会将您在文本文件中提供的事件放置在日历上。据我所知,该脚本很少(如果不是没有)将正常的节假日放置在日历上,除非您将它们包含在文本文件中。

多年来,我从 UNIX 转向 Windows 世界,在此过程中,我将 PsCal 重写为 Windows 命令 shell 应用程序。在此过程中,我还对原始实现进行了相当多的更改和增强。

在深入探讨 PsCal *如何*创建日历的细节之前,我想花一点篇幅介绍 PsCal 中使日历与您(用户)相关的部分——用户事件文件。

用户事件文件

很快,您将学习如何轻松创建示例**用户事件文件**。此文件的目的是演示您可以使用 PsCal 执行的一些操作,并为您提供一个注释良好的文件,您可以编辑该文件以创建与您相关的日历。

用户事件文件中可以出现两种一般类型的事物:要放置在日历上的事件和影响日历整体外观的事物。

用户事件定义

用户事件是与您相关的、您希望放置在日历上的内容。您可能希望在日历上添加生日和纪念日。您还可以添加诸如约会、音乐会或只发生一次的日期等内容。

用户事件的格式是

<month>:<day>[;<option1>[;<option2>[;...]]]:<text>

月份可以是 1-12 或 *,而日期可以是 1-31 或 `<daycode>`。要打印的文本是行上的最后一项。选项可以出现在日期/日码和文本之间,用分号与日期/日码和彼此分隔。

PsCal 的一个不错的功能对于每年重复发生的任何类型的事件(如生日)都很有用。如果事件要打印的文本包含一个由感叹号包围的 4 位年份,那么当事件放置在日历上时,该文本将被替换为类似“25th”的内容。例如,如果某个事件的文本是`"Dad's !1962! BDay"`,并且该事件被添加到 2023 年的日历中,则放置在日历上的文本将是`"Dad's 61st BDay"`。同一个事件出现在 2000 年的日历上将显示为`"Dad's 38th BDay"`

如您所见,此功能将使您了解人们的年龄,以及某个特殊事件的周年纪念日。

事件的格式在您可以创建的示例事件文件中进行了详细注释,因此我不会在这篇文章中赘述更多细节。但是,有必要简要讨论一下可用的选项。此外,在关于漫游假期的后续部分中,我将阐明可以出现在`<day>`位置的`<daycode>`构造。

事件选项

有几个选项可以修改日历日的默认外观。其中两个,`font` 和 `fmt`,可以更改用于打印事件文本的字体和文本的对齐方式(左对齐、右对齐、居中或两端对齐)。

其他选项包括:`year` 用于在特定年份发生的事件,`repeat` 用于每隔几天重复发生的事件,以及 `gfn`,它是图形函数的缩写。有几种这样的图形函数可以单独使用或相互结合使用以“美化”事件。

修改日历外观

除了事件,用户事件文件还可以包含影响日历整体外观的项目。这些项目都以“`@`”开头,它们包括诸如要使用的字体、默认事件文本格式、页眉和页脚文本以及可能包含图形函数和/或图像的 PostScript 代码。

与事件一样,这些类型的项目在示例事件文件中进行了详细注释,因此此处不再对其进行更多说明。相反,让我们看看 PsCal 为创建个性化日历所采取的步骤。

PsCal 如何创建日历

PsCal 采用的日历生成过程由三个主要部分组成。它始于一个接受几个可选参数的 Windows 批处理文件。这些可选参数可以以各种方式修改要创建的日历。

批处理文件将其参数传递给 GAWK,使其执行 *pscal.awk* 文件中包含的程序。此 AWK 程序创建所请求日历的 PostScript 版本,并向批处理文件返回一个值,指示成功或失败。

在成功的情况下,批处理文件会执行 GhostScript 安装中的 *ps2pdf* 批处理文件。该批处理文件将 GAWK 创建的 PostScript 文件转换为 PDF 文件。

MakeCalendar.bat

要创建当前年份的日历,您只需执行不带参数的 *MakeCalendar* 批处理文件。默认情况下,它将在名为 *<current year>.pdf* 的文件中为当前年份创建 12 个月的日历。

此外,默认情况下,如果当前目录中存在名为 *events.pse* 的文件,则该文件中的事件将被放置在日历上。除非您在命令行中提供不同文件的名称,否则只要该文件存在于当前目录中,就会使用该文件。*MakeCalendar.bat* 的用法是

MakeCalendar [OPTIONS]

OPTIONS can be one or more of:

  -y=<Year>       The year for which the calendar is to be generated. DEFAULT:
                  the current year.

  -m=<MonthList>  A list of months to be included in the calendar.  DEFAULT:
                  1-12.  Months are numbered from 1 (Jan) through 12 (Dec).
                  Specify a range of months by placing a dash (-) between two
                  months. Use a comma (,) to separate individual months and
                  ranges of months.

  -e=<EventsFile> The name of a file containing events specific to you, such as
                  birthdays, anniversaries, paydays, etc. DEFAULT: a file named
                  "events.pse" in the current directory, if it exists.  This
                  file can also contain data that can modify the appearance of
                  the calendar, such as headers and footers, fonts, and various
                  graphics effects.

  -n=<BaseName>   The base name of two output files that are created. One is
                  a PostScript file (which is deleted by default), and one is a
                  PDF file. Each of them will contain the calendar for the year
                  in (obviously) different formats.  DEFAULT: the current (or
                  given) year, producing <year>.ps and <year>.pdf.

  -example        Create an example events file you can edit to add events like
                  birthdays, anniversaries, appointments, paydays, etc. to the
                  calendars you create.

就 Windows 批处理而言,`MakeCalendar` 相当简单。它使用的更有用的批处理功能之一是将环境变量设置为某个程序输出的值。例如,这一行

for /f %%y in ('gawk "BEGIN{print strftime(\"^%%Y\")}"') do set BASENAME=%%y

将变量 `BASENAME` 设置为当前年份,该年份由执行 `strftime()` 函数的 GAWK 输出。

pscal.awk

PsCal 的 AWK 部分执行的大部分工作是创建一个 PostScript 数组,该数组定义将出现在日历上的事件。这些事件包括一些美国和世界各地的假期,以及在要使用的任何事件文件中定义的事件。出于所有实际目的,在与 PsCal 结合使用时,*事件*和*假期*是可互换的术语。

解析用户事件文件

当提供了用户事件文件以供使用时,它会在函数 `GatherEventsAndData()` 开头的代码中进行顶级解析。

# Start with any user-supplied events, code, and/or configuration settings.
if (length(UserEventsFile)) {
    while (ReadLine(UserEventsFile) > 0) {
        if ($1 ~ /^@/) {        # Handle lines that begin with "@".
            HandleCommandOrCode(UserEventsFile,FontArray,EventArray)
        }
        else {                  # Handle lines that should contain an event.
            HandleUserEvent(UserEventsFile,EventArray)
        }
    }
    close(UserEventsFile)
}

`ReadLine()` 函数从用户事件文件中读取行,直到它找到一个非空白且不是注释的行(即,它的第一个非空白字符不是“`#`”),或者它到达文件末尾(向调用者返回 0)。

当 `ReadLine()` 返回一行时,该行可能以“`@`”开头,在这种情况下,它应该包含可以或将以某种方式影响日历外观的信息。如果它以其他字符开头,则它*应该*包含事件描述。

`if` 测试 `$1 ~ /^@/` 是直接的 AWK 代码。它测试行上第一个非空白字符的`字符串` (`$1`) 是否匹配 (`~`) 正则表达式 (`/^@/`)。该正则表达式表示文本以 (`^`) ` @` 字符开头。

因此,如果返回行的第一个非空白字符是“`@`”,则该行由函数 `HandleCommandOrCode()` 处理。该函数处理文件中的字体定义(保存在 `FontArray[]` 中)、日历页眉和页脚以及可能包含图形函数和用户定义图像的 PostScript 代码等内容。它还处理可能定义的任何重复事件组,调用函数 `HandleUserEvent()` 来完成组中每个事件的工作。

用户事件文件中的非“`@`”行传递给函数 `HandleUserEvent()`。此代码检查该行是否确实描述了一个事件。如果是,并且事件应包含在日历中,则将其添加到 `EventArray[]`。事件被排除的唯一原因是

  1. 事件仅发生在某个特定年份,而该年份不是正在创建的日历的年份
  2. 事件不包含任何会产生可见效果的信息,或者
  3. 该行未被识别为有效事件。

一旦用户事件文件被处理(如果提供了的话),AWK 脚本会将各种节假日,有时还有月相,添加到 `EventArray[]` 中。

漫游节假日和事件

许多节假日,如元旦和圣诞节,每年都发生在同一日期,因此描述它们何时出现在日历上是直截了当的。然而,还有其他节假日,如感恩节和选举日等事件,它们的日期每年都不同。此外,在此类别中还有事件,例如,发生在每月最后一个工作日。

`daycode` 构造能够处理许多此类事件。它的形式是 `<occurrence>,<day of week>`,这两个项都是单个数字整数。

`<occurrence>` 的范围是 1(第一个)到 5(第五个)或 9(最后一个);`<day of the week>` 的范围是 0(星期日)到 6(星期六)或 9(工作日)。这两个数字范围共同允许您指定,例如,每月的第二个星期日 (2,0)、最后一个星期四 (9,5) 或最后一个工作日 (9,9) 等日期。

添加对您重要的缺失节假日

PsCal 中包含的一些节假日与美国相关。但是,将其他节假日添加到 *pscal.awk* 程序中非常容易。

函数 `AddStandardHolidays()` 是您添加重要缺失节假日或删除对您没有特定意义的节假日的地方。以下是该函数中定义两个美国节假日的众多行中的两行

EventArray[++gEventCount] = SepSprint("07","04",";gfn=Gbottom_text;","!1776! 
                            Independence Day")
EventArray[++gEventCount] = SepSprint("02", ConvertDayCode(gYear,2,"3,1"), 
                            ";gfn=Gbottom_text;","Presidents' Day")

这些行将 `SepSprint` 函数的返回值分配给 `EventArray[]` 中的条目。`SepSprint` 只是返回一个包含其输入`字符串`的 `字符串`,这些`字符串`由存储在全局变量 `gSep` 中的另一个`字符串`:“` ``` `”分隔。因此,上面的第一行等效于

EventArray[++gEventCount] = "07```04```;gfn=Gbottom_text;```!1776! Independence Day" 

这只是将多个 `字符串` 存储在单个数组条目中的一种便捷方式,以便可以使用 AWK 的各种功能轻松地恢复它们。如果您查看函数 `WriteEventData()` 开头的代码,您可以看到恢复是如何完成的。

在这种情况下传递给 `SepSprint` 的`字符串`依次是:月份、日期、用于将文本放置在日期日历方块底部的图形函数的引用,以及标识节假日的文本。

请注意,第二个赋值没有(直接)指定总统日发生的月份日期。由于这一天发生在二月的第三个星期一,因此使用 `ConvertDayCode()` 函数将其代码转换为日历年份 (`gYear`) 的月份日期。

希望通过本次讨论可以清楚地知道,如果您想将英国五月初的银行假日添加到您的日历中,可以通过在函数 `AddStandardHolidays()` 中添加如下一行来实现

EventArray[++gEventCount] = SepSprint("05", ConvertDayCode(gYear,5,"1,1"), 
                            ";gfn=Gbottom_text;","Bank holiday")

安排事件

在 PostScript 中,页面上绘制的任何内容都会完全遮挡之前可能绘制的任何内容。因此,如果某个日历日恰好定义了多个图形函数,则需要以提供最佳整体外观的顺序应用这些函数。

一旦所有年份的事件都添加到 `EventArray[]` 中,函数 `ArrangeEvents()` 将通过调用另外两个函数:`FindDaysWithMultipleEvents()` 和 `RearrangeMultiEventDays()` 来进行任何必要的调整

# Search EventArray[] to create an array, M[], of size MultiEventDayCount.
# Its entries will be the starting and ending indices of 2 or more events
# in EventArray[] that occur on some single day.
MultiEventDayCount = FindDaysWithMultipleEvents(EventArray,M)

# Now rearrange any days in EventArray[] that have multiple events.
if (MultiEventDayCount > 0) {
    RearrangeMultiEventDays(EventArray,M,MultiEventDayCount)
} 

`FindDaysWithMultipleEvents()` 按月和日对 `EventArray[]` 中的条目进行排序。然后它遍历数组,查找相同月日上的多个条目。

如果找到了一些,它会创建另一个数组 `M[]`,该数组包含 `EventArray[]` 中在同一天发生的事件的起始和结束索引对。例如,如果 `EventArray[]` 的第 5 到第 8 个条目发生在同一天,那么 `M[]` 中将存在某个条目,使得 `M[i] = "5 8"`。`FindDaysWithMultipleEvents()` 的返回值是 `M[]` 中的条目数,即发生两个或更多事件的天数。

如果 `M[]` 中有任何条目,则调用 `RearrangeMultiEventDays()` 以确保每天的多个事件以尽可能最好的方式排序。然而,实现这一壮举有些复杂。对于每个具有多个事件的日子

  • 将该日期的事件复制到另一个数组中。
  • 按用户事件文件中事件定义的行号对该数组进行排序(以便以用户在文件中放置它们的顺序打印带有文本的多个事件)。
  • 重复地在副本中查找按优先级顺序使用单个图形函数的事件。找到第一个后,将事件复制到 `EventArray[]` 中该日期的第一个条目。继续这样做,直到所有使用的图形函数都已找到并复制到 `EventArray[]` 中的下一个位置。如果副本中仍有任何事件,只需按顺序将它们复制到 `EventArray[]` 中的其余条目中。
  • 如果某天有用户定义的图形(即“`gfn=StartImage ...`”),请确保没有位图或阴影在其上绘制,方法是简单地删除位图或阴影事件(如果存在)。
  • 最后,如果存在使用 `Gbottom_text` 函数的多个事件,将它们合并为一个事件,并用“`;`”分隔各种文本。

这两个函数及其调用的函数的效果是,例如,将用户事件文件中这些 6 月 16 日的事件定义从以下内容重新排列

...
6:16;gfn=Gburst_day;fmt=c;font=BigBoldItalic:!1984! \n Annual \n Special \n Day
6:16;gfn=2 Gbox_day
6:16;gfn=3 G3D_day
... 

转换为 PostScript 事件数组中的这些事件条目

...
[  6 16 fmt_c  0 {3 G3D_day} () ]
[  6 16 fmt_c  4 {Gburst_day} () ]
[  6 16 fmt_c  0 {2 Gbox_day} () ]
[  6 16 fmt_c  4 {} (36th \n Annual \n Special \n Day) ]
... 

`G3D_day` 图形函数用于使日历日突出显示(仿佛是 3D 的)。使用它时,需要在任何其他函数之前应用。因此,即使它在用户事件文件中是 6 月 16 日列出的最后一个函数,它也成为该日期的 PostScript 事件数组中的第一个事件。`Gburst_day` 函数的优先级高于 `Gbox_day`,因此它们保持其原始顺序。

您可能已经注意到,与 `Gburst_day` 函数一起指定的文本已被分离到一个“纯文本”事件中,生成了四个 PostScript 事件数组条目。这样做是为了确保在日历日上可能出现的任何图形函数之后,事件的文本最后打印。

PsCal PostScript 代码

如果您想查看日历的 PostScript 代码,您可以修改 *MakeCalendar* 批处理文件中的一行,将变量 `DEL_PS_FILE` 的设置从 1 更改为 0。这样做之后,批处理文件将不再删除它创建的任何日历的 PostScript 版本。

用于制作日历的一些 PostScript 代码由 AWK 脚本创建:文件开头的样板代码、文件末尾与页面生成相关的代码、事件数组以及用户事件文件中包含的任何字体数据或 PostScript 代码。

然而,大部分 PostScript 代码与日历无关,只是简单地从 postscript 目录中的两个文件复制而来:*PsStart.txt* 和 *PsEnd.txt*。这些代码最初由 Pipeline Associates, Inc. 的 Patrick Wood 编写,这个实体似乎已不复存在。

Copyright 1987 by Pipeline Associates, Inc.
Permission is granted to modify and distribute this free of charge, as
long as the above copyright notices and this statement are included. 

多年来,我对这段代码进行了多次更改和添加。例如,我添加了与以下内容相关的所有 PostScript 代码

  • 位图和图像
  • 用于“美化”选定日期的图形功能
  • 月相
  • 在要打印的文本中嵌入换行符
  • 显示一年中的第几天以及日期
  • 生成一整年(12 页)的日历,而不是单个月份(1 页)

不幸的是,原始 PostScript 代码中的注释有些不足。我在需要解密某些内容才能进行添加或修改的地方添加了更多注释,但我尚未将代码提升到更高的标准,无论是在代码的格式还是注释方面。我深表歉意。

此外,如果您决定深入研究日历的 PostScript 代码,请注意代码使用大量全局变量,并且没有可辨别的命名约定来帮助您识别它们。最后,由于 PostScript 的基于堆栈的性质,有些地方您需要对操作数堆栈上的内容有很好的记忆。

主要 PostScript 代码

PostScript 不允许执行尚未定义的函数。因此,您需要滚动到 PostScript 文件的末尾才能查看它试图完成什么。

对于 PsCal,您会找到由 *pscal.awk* 中的 `CreateMonthPages()` 函数创建的一些代码。以下是该代码的示例

%%Page: January 1
1 DoOneMonth
showpage

%%Page: February 2
2 DoOneMonth
showpage

%%Page: March 3
3 DoOneMonth
showpage

/month 4 def
/DayOfYear DayOfYear ndays add def

/month 5 def
/DayOfYear DayOfYear ndays add def

%%Page: June 4
6 DoOneMonth
showpage

... 

`%%` 双字符开头是一个文档结构注释 (DSC)。对于 `%%Page:`,月份名称是页面的名称,其后的整数是文件中所有页面中的页码。在上面的示例中,六月是第四页,因为四月和五月没有包含在这个特定的日历中。

由于此特定日历中排除了四月和五月,因此只需要将这些月份的天数添加到全局变量 `DayOfYear` 中,该变量用于在日期附近打印一年中的第几天。

关于四月和五月代码不明显的是,`ndays` 是一个计算月份天数的函数。同样不明显的是,该函数需要定义变量 `month` 才能返回正确的数字。

文件中此部分为每个包含的月份调用另外两个函数:`DoOneMonth` 和 `showpage`。`DoOneMonth` 在调用时需要在堆栈上有一个整数。此整数表示它应该创建页面的年份月份。每当包含一个月份时,都会调用 PostScript `showpage` 过程以在输出设备上形成页面。

函数 DoOneMonth

此函数创建表示月份的图像。这将包括包含在 `/holidays` 数组(由 *pscal.awk* 程序根据 `EventArray[]` 的内容创建)中的月份的事件和节假日。

`DoOneMonth` 调用另一个主要函数 `calendar`,不仅绘制月份的大型全页日历,还绘制前一个月和后一个月的两个迷你日历。`DoOneMonth` 设置一个变量 `IsSubMonth` 来控制 `calendar` 的行为,并充分利用 PostScript 的 `scale` 和 `translate` 运算符来放置和缩小迷你日历及其文本。

函数 drawevents

当 `calendar` 函数正在绘制某个月份的主日历时,会调用此函数;它遍历 `/holidays` 数组,查找当前月份发生的事件。找到后,它会调用 `prtevent` 函数来处理单个事件文本的打印。此时的 PostScript 代码变得相当复杂,涉及各种类型的文本对齐和将较长的事件文本分成多行。如果您足够受虐狂,您可能会喜欢浏览这段代码!

PostScript 到 PDF 转换

多年来,我一直使用 `GSview` 来查看我创建的日历。一段时间前,我发现 GhostScript 可以将 PostScript 文件转换为 PDF 文件。由于 PDF 是一种广泛支持的格式,我花了一些精力将 GhostScript 集成到 PsCal 中。

不幸的是,GhostScript 的安装文件相当大,所以我只提供了关于如何下载并将其添加到 PsCal 安装的信息。您可以在 PsCal 压缩文件中的 * _readme.txt* 文件中阅读如何执行此操作。

一路上的趣事

多年来,我在 PsCal 上工作时,既有乐趣也有挫折。我在这里简要提及一些。

数字结尾

我必须弄清楚的一件有趣的事情是如何为一系列中的第 N 个数字返回正确的结尾。也就是说,应该将哪个正确的 2 字符字符串附加到各种数字上?例如,第 50 个、第 1 个、第 133 个等等。

此任务由我的一个库函数 `LibGetNumberEnding()` 完成,该函数位于 *Math.awk* 库中。这看起来很简单,但我确实花了一些心思才弄对(提示:需要处理十几岁)。

您可能会喜欢尝试用您选择的语言编写一些代码来完成这项任务。

一年中的第 N 天在哪个月?

这个问题在 PsCal 需要时由我的另一个库函数 `LibMonthContainingDayOfYear()` 提供,该函数位于 *TimeAndDate.awk* 库中。

这个函数的工作方式没有什么特别之处,但看看你是否能想出一种基于不同方法来做到这一点的方法会很有趣。

复活节

有些事情我需要知道才能让 PsCal 达到我想要的状态,而我无法独立想出。如果你曾深入研究过复活节假期,特别是它何时发生,那么你可能知道这是怎么回事。

*pscal.awk* 文件中的 `AddEasterAndRelated()` 函数负责将复活节及其相关节日添加到事件数组中。我为此函数编写的注释包括:“*最离奇的计算节日——复活节,落在3月21日或之后的第一个教会满月(又称逾越节满月,PFM)后的第一个星期日。*”

这很好地告诉我,我将无法弄清楚复活节在任何给定年份何时发生。幸运的是,我能够在这里找到一个可以这样做的算法: http://almanac.oremus.org/easter/computus/

结论

尽管 AWK 和 PostScript 已经存在了几十年,但它们仍然可以用于工作和娱乐。我希望 PsCal 能很好地证明它们的实用性,并且 PsCal 的 *postscript\examples* 目录中的 PostScript 程序能展示您所能获得的一些乐趣。

我相信大多数程序员都能从 AWK 的实际知识中受益。我知道,如今对许多人来说,使用除了*当日语言*之外的任何东西都是令人厌恶的,但有些任务用 AWK 可以如此简单地完成,以至于它至少值得一点点考虑。

有多少种语言如此简洁,可以用比这更小的程序打印一个或多个文件的内容以及行号

{print FNR,$0}

我承认我不知道那个问题的答案,但我怀疑如果它不是零,它也会相当小。

历史

  • 2023年1月6日:初始版本
© . All rights reserved.