字母计数法 Unix split 命令源码阅读与分析

「字母序列」计数法,其实类似于26进制计数系统,只是使用字母代替数字

在这种系统中,每个位置可以取从az的任何字母,其中a代表0,b代表1,以此类推,直到z代表25。当一个位置上的字母超过z时,就像在十进制中一样进位,但是是从aa开始而不是从10开始。

这种计数法经常用于生成有序的字符串序列,例如:

  • 最常见的是Excel的列标题
  • split命令生成的文件后缀,参考前文
  • 以及Jvet会议序号。

前文编写了字母计数法和阿拉伯数字的转换脚本,对ascii码直接强硬转换,比较僵硬(只适用于26-676范围内的split生成文件) 而且扩展性较差。这次遂来分析一下Linux系统中的split命令是通过怎么样的算法实现的。

split 是 coreutils 的一部分,也是GNU操作系统中的基本文件。源代码库可以在这里看到。可以看到,在 split 命令的源代码中,生成字母顺序递增的后缀(例如 "aa", "ab", "ac" 等)的逻辑是由next_file_name 函数实现的。

c 复制代码
static void
next_file_name (void)
{
  /* Index in suffix_alphabet of each character in the suffix.  */
  static idx_t *sufindex;
  static idx_t outbase_length;
  static idx_t outfile_length;
  static idx_t addsuf_length;
​
​
  if (! outfile)
    {
      bool overflow, widen;
​
​
new_name:
      widen = !! outfile_length;
​
​
      if (! widen)
        {
          /* Allocate and initialize the first file name.  */
​
​
          outbase_length = strlen (outbase);
          addsuf_length = additional_suffix ? strlen (additional_suffix) : 0;
          overflow = ckd_add (&outfile_length, outbase_length + addsuf_length,
                              suffix_length);
        }
      else
        {
          /* Reallocate and initialize a new wider file name.
             We do this by subsuming the unchanging part of
             the generated suffix into the prefix (base), and
             reinitializing the now one longer suffix.  */
​
​
          overflow = ckd_add (&outfile_length, outfile_length, 2);
          suffix_length++;
        }
​
​
      idx_t outfile_size;
      overflow |= ckd_add (&outfile_size, outfile_length, 1);
      if (overflow)
        xalloc_die ();
      outfile = xirealloc (outfile, outfile_size);
​
​
      if (! widen)
        memcpy (outfile, outbase, outbase_length);
      else
        {
          /* Append the last alphabet character to the file name prefix.  */
          outfile[outbase_length] = suffix_alphabet[sufindex[0]];
          outbase_length++;
        }
​
​
      outfile_mid = outfile + outbase_length;
      memset (outfile_mid, suffix_alphabet[0], suffix_length);
      if (additional_suffix)
        memcpy (outfile_mid + suffix_length, additional_suffix, addsuf_length);
      outfile[outfile_length] = 0;
​
​
      free (sufindex);
      sufindex = xicalloc (suffix_length, sizeof *sufindex);
​
​
      if (numeric_suffix_start)
        {
          affirm (! widen);
​
​
          /* Update the output file name.  */
          idx_t i = strlen (numeric_suffix_start);
          memcpy (outfile_mid + suffix_length - i, numeric_suffix_start, i);
​
​
          /* Update the suffix index.  */
          idx_t *sufindex_end = sufindex + suffix_length;
          while (i-- != 0)
            *--sufindex_end = numeric_suffix_start[i] - '0';
        }
​
​
#if ! _POSIX_NO_TRUNC && HAVE_PATHCONF && defined _PC_NAME_MAX
      /* POSIX requires that if the output file name is too long for
         its directory, 'split' must fail without creating any files.
         This must be checked for explicitly on operating systems that
         silently truncate file names.  */
      {
        char *dir = dir_name (outfile);
        long name_max = pathconf (dir, _PC_NAME_MAX);
        if (0 <= name_max && name_max < base_len (last_component (outfile)))
          error (EXIT_FAILURE, ENAMETOOLONG, "%s", quotef (outfile));
        free (dir);
      }
#endif
    }
  else
    {
      /* Increment the suffix in place, if possible.  */
​
​
      idx_t i = suffix_length;
      while (i-- != 0)
        {
          sufindex[i]++;
          if (suffix_auto && i == 0 && ! suffix_alphabet[sufindex[0] + 1])
            goto new_name;
          outfile_mid[i] = suffix_alphabet[sufindex[i]];
          if (outfile_mid[i])
            return;
          sufindex[i] = 0;
          outfile_mid[i] = suffix_alphabet[sufindex[i]];
        }
      error (EXIT_FAILURE, 0, _("output file suffixes exhausted"));
    }
}

可以看到,有几个关键的变量:

  1. sufindex:指针数组,存储当前后缀中每个字符在字母表suffix_alphabet中的索引。
  2. outbase_length:输出文件基本名(不包括后缀)的长度。
  3. outfile_length:输出文件名的总长度(包括基本名和后缀)。
  4. addsuf_length:额外的后缀的长度。
  5. outfile:字符指针,指向当前正在构建的输出文件名。
  6. outfile_midoutfile中指向后缀开始位置的指针。

代码逻辑分为两部分:

  • 初始化新文件名:

    • 如果是第一次调用,分配并初始化文件名。
    • 如果需要扩展后缀(即已经用完了所有可能的后缀组合),则重新分配更长的文件名,并将生成的后缀的不变部分合并到前缀中,然后重新初始化一个更长的后缀。
  • 增加现有后缀:

    • 在现有后缀上递增,如果可能的话,直接在原地完成。
    • 如果当前字符已经是suffix_alphabet中的最后一个字符,那么将该位置重置为suffix_alphabet的第一个字符,并向前进位。
    • 如果所有的后缀字符都已经用完,那么会报错,提示输出文件后缀已耗尽。

具体来说,整个函数的工作流程是首先检查是否需要创建一个新的文件名(如果outfile为空),然后尝试在现有的后缀上递增;如果递增失败(因为已经达到最大可能的后缀),则扩展后缀长度并重新开始。

嗯,,比想象中多坑,,

相关推荐
wuweijianlove1 天前
算法复杂度估算的实验建模与可视化表达的技术6
算法
执笔画流年呀1 天前
7大排序算法
java·算法·排序算法
AI成长日志1 天前
【算法学习专栏】动态规划基础·中等两题精讲(198.打家劫舍、322.零钱兑换)
学习·算法·动态规划
计算机安禾1 天前
【数据结构与算法】第28篇:平衡二叉树(AVL树)
开发语言·数据结构·数据库·线性代数·算法·矩阵·visual studio
测试_AI_一辰1 天前
AI 如何参与 Playwright 自动化维护:一次自动修复闭环实践
人工智能·算法·ai·自动化·ai编程
三万棵雪松1 天前
【Linux 物联网网关主控系统-Web部分(一)】
linux·前端·嵌入式linux
未来之窗软件服务1 天前
算法设计—计算机等级考试—软件设计师考前备忘录—东方仙盟
算法·软件设计师·计算机等级考试
未来之窗软件服务1 天前
哈夫曼树构造—计算机等级考试—软件设计师考前备忘录—东方仙盟
算法·软件设计师·计算机等级考试·仙盟创梦ide·东方仙盟
似水এ᭄往昔1 天前
【Linxu】--进程优先级和进程切换
linux·运维·服务器
SUNNY_SHUN1 天前
VLM走进农田:AgriChat覆盖3000+作物品类,607K农业视觉问答基准开源
论文阅读·人工智能·算法·开源