如何查找下一个可用文件后缀(file_a.txt file_b.txt 等)

如何查找下一个可用文件后缀(file_a.txt file_b.txt 等)

每次发生特定事件时,我的系统都会创建一个新的文本文件。
文件应命名file_a.txt file_b.txt file_c.txt等。

在 Bash shell 脚本中,如何找出接下来应使用的文件名?

例如,如果file_a.txtfile_b.txt存在但不存在file_c.txt,则下一个可用文件名是file_c.txt

如果更容易的话,这可以是一个数字。
我开始设计一种算法,但可能有更简单的方法吗?

注意:文件每天都会被删除,因此到达的概率z为零。所以,z任何策略都是可以接受的:aa使用整数,甚至使用UUID。

答案1

这是纯粹在 bash 中执行此操作的粗略方法(没有错误检查):

#helper function to convert a number to the corresponding character
chr() {
  [ "$1" -lt 256 ] || return 1
  printf "\\$(printf '%03o' "$1")"
}

#helper function to convert a character to the corresponding integer
ord() {
  LC_CTYPE=C printf '%d' "'$1"
}

#increment file
fn_incr(){

  #first split the argument into its constituent parts

  local fn prefix letter_and_suffix letter suffix next_letter
  fn=$1
  prefix=${fn%_*}
  letter_and_suffix=${fn#${prefix}_}
  letter=${letter_and_suffix%%.*}
  suffix=${letter_and_suffix#*.}

  #increment the letter part
  next_letter=$(chr $(($(ord "$letter") + 1)))

  #reassemble
  echo "${prefix}_${next_letter}.${suffix}"
}

用法示例:

fn_incr foo_bar_A.min.js
#=> foo_bar_B.min.js

在 bash 中使用多字母索引执行此操作将需要更长的代码。您始终可以在不同的可执行文件中执行此操作,但是您可能希望批量增加文件名,否则可执行文件的启动开销可能会导致程序速度减慢,令人无法接受。这一切都取决于您的用例。

在这里使用普通的旧整数可能是更好的选择,因为您不必手动管理 9++ 如何向左溢出。


chr()并被ord()无耻地偷走用于获取字母表 ASCII 值的 Bash 脚本

答案2

如果你真的不在乎,在 Linux 上(更准确地说,使用GNU 核心工具):

tmpfile=$(TMPDIR=. mktemp --backup=numbered)
… # create the content
mv --backup=numbered -- "$tmpfile" file.txt

这使用了GNU备份命名方案file.txt、、、、……file.txt.~1~file.txt.~2~

另一种相对紧凑的方法是利用可以将数字放置在更方便的位置的优势zsh 的 glob 限定符找到最新的文件,并用一些计算下一个文件参数扩展

latest=(file_<->.txt(n[-1]))
if ((#latest == 0)); then
  next=file_1.txt
else
  latest=$latest[1]
  next=${${latest%.*}%%<->}$((${${latest%.*}##*[^0-9]}+1)).${latest##*.}
fi
mv -- $tmpfile $next

对于任何 POSIX shell,如果您使用带前导零的数字,您会更轻松。请注意,带有前导零的整数文字将被解析为八进制。

move_to_next () {
  shift $(($#-2))
  case ${1%.*} in
    *\*) mv -- "$2" file_0001.txt;;
    *)
      set -- "${1%.*}" "${1##*.}" "$2"
      set -- "${1%_*}" "$((1${1##*_}+1)).$2" "$3";;
      mv -- "$3" "${1}_${2#1}";;
  esac
}
move_to_next file_[0-9]*.txt "$tmpfile"

答案3

尝试:

perl -le 'print $ARGV[-1] =~ s/[\da-zA-Z]+(?=\.)/++($i=$&)/er' file*.txt

这会给你file_10.txtafter file_9.txtfile_g.txtafter file_f.txtfile_aa.txtafter file_z.txt,但不是file_ab.txtafterfile_aa.txtfile_11.txtafterfile_10.txt因为file*shell glob 会排序file_z.txt file_aa.txt之后。file_9.txtfile_10.txt

zsh您可以通过使用file*.txt(n)而不是来解决后一个问题file*.txt

或者您可以在 中定义数字排序顺序zsh,基于这些aaabc被识别为基数 36 中的数字:

b36() REPLY=$((36#${${REPLY:r}#*_}))
perl ... file_*.txt(no+b36)

(请注意,顺序是 ...7, 8, 9, a/A, b/B..., z/Z, 10, 11... 所以您不想混合file_123.txtfile_aa.txt)。

答案4

使用模块python中可用的各种迭代器构建块可以轻松解决这个问题itertools

from os.path import isfile
from string import ascii_lowercase
from itertools import dropwhile, imap, chain, product, repeat, count
next(dropwhile(isfile, imap('file_{}.txt'.format, 
    imap(''.join, chain.from_iterable(
    product(ascii_lowercase, repeat=x) for x in count(1))))))

相关内容