按基本名称对文件的路径名数组进行排序

Tim*_*Tim 9 bash array filenames sort

假设我有存储在数组中的文件路径名列表

filearray=("dir1/0010.pdf" "dir2/0003.pdf" "dir3/0040.pdf" ) 
Run Code Online (Sandbox Code Playgroud)

我想根据文件名的基名按数字顺序对数组中的元素进行排序

sortedfilearray=("dir2/0003.pdf" "dir1/0010.pdf" "dir3/0040.pdf") 
Run Code Online (Sandbox Code Playgroud)

我怎样才能做到这一点?

我只能对它们的基本名称部分进行排序:

basenames=()
for file in "${filearray[@]}"
do
    filename=${file##*/}
    basenames+=(${filename%.*})
done
sortedbasenamearr=($(printf '%s\n' "${basenames[@]}" | sort -n))
Run Code Online (Sandbox Code Playgroud)

我在想

  • 创建一个关联数组,其键是基名,值是路径名,因此对路径名的访问始终通过基名完成。
  • 仅为 basename 创建另一个数组,并应用于sortbasename 数组。

谢谢。

Gow*_*ham 10

sort在 GNU coreutils 中允许自定义字段分隔符和键。您设置/为字段分隔符并根据第二个字段进行排序以对基本名称而不是整个路径进行排序。

printf "%s\n" "${filearray[@]}" | sort -t/ -k2 会产生

dir2/0003.pdf
dir1/0010.pdf
dir3/0040.pdf
Run Code Online (Sandbox Code Playgroud)

  • 这是 `sort` 的标准选项,而不是 GNU 扩展。如果路径的长度都相同,这将起作用。 (4认同)
  • 这仅在每个路径包含一个目录时才有效。“some/long/path/0011.pdf”怎么样?据我从其手册页中看到,`sort` 确实不包含按最后一个字段排序的选项。 (3认同)

Kus*_*nda 6

oldIFS="$IFS"; IFS=$'\n'
if [[ -o noglob ]]; then
  setglob=1; set -o noglob
else
  setglob=0
fi

sorted=( $(printf '%s\n' "${filearray[@]}" |
            awk '{ print $NF, $0 }' FS='/' OFS='/' |
            sort | cut -d'/' -f2- ) )

IFS="$oldIFS"; unset oldIFS
(( setglob == 1 )) && set +o noglob
unset setglob
Run Code Online (Sandbox Code Playgroud)

对名称中包含换行符的文件名进行排序将导致该sort步骤出现问题。

它生成一个/以 - 分隔的列表,其中awk包含第一列中的基本名称和其余列中的完整路径:

0003.pdf/dir2/0003.pdf
0010.pdf/dir1/0010.pdf
0040.pdf/dir3/0040.pdf
Run Code Online (Sandbox Code Playgroud)

这就是排序的内容,cut用于删除第一个/- 分隔的列。结果被转换成一个新的bash数组。


Rom*_*est 5

与排序GAWK表达(通过支持的bashreadarray):

包含空格的文件名示例数组:

filearray=("dir1/name 0010.pdf" "dir2/name  0003.pdf" "dir3/name 0040.pdf")
Run Code Online (Sandbox Code Playgroud)
readarray -t sortedfilearr < <(printf '%s\n' "${filearray[@]}" | awk -F'/' '
   BEGIN{PROCINFO["sorted_in"]="@val_num_asc"}
   { a[$0]=$NF }
   END{ for(i in a) print i}')
Run Code Online (Sandbox Code Playgroud)

输出:

echo "${sortedfilearr[*]}"
dir2/name 0003.pdf dir1/name 0010.pdf dir3/name 0040.pdf
Run Code Online (Sandbox Code Playgroud)

访问单个项目:

echo "${sortedfilearr[1]}"
dir1/name 0010.pdf
Run Code Online (Sandbox Code Playgroud)

假设没有文件路径包含换行符。请注意,值的数字排序@val_num_asc仅适用于键的前导数字部分(在此示例中没有),并回退到词法比较(基于strcmp(),而不是语言环境的排序顺序)以进行关系。


ale*_*xis 5

由于“dir1dir2是任意路径名”,我们不能指望它们由单个目录(或相同数量的目录)组成。所以我们需要将路径名中的最后一个斜杠转换为路径名中其他地方没有的东西。假设字符@没有出现在您的数据中,您可以按基本名称排序,如下所示:

cat pathnames | sed 's|\(.*\)/|\1@|' | sort -t@ -k+2 | sed 's|@|/|'
Run Code Online (Sandbox Code Playgroud)

第一个sed命令用选定的分隔符替换每个路径名中的最后一个斜杠,第二个命令反转更改。(为简单起见,我假设路径名可以每行一个。如果它们在 shell 变量中,首先将它们转换为每行一个格式。)


Sté*_*las 4

与 ksh 或 zsh 相反,bash 没有对数组或任意字符串列表进行排序的内置支持。alias它可以对 glob 或orset的输出进行排序typeset(尽管最后 3 个不在用户的区域设置排序顺序中),但这实际上不能在这里使用。

\n\n

POSIX 工具箱中没有任何东西可以轻松地对任意字符串列表进行排序\xc2\xb9(sort对行进行排序,因此只有除 NUL 和换行符之外的短字符序列(LINE_MAX 通常比 PATH_MAX 短),而文件路径是除 0 之外的非空字节序列)。

\n\n

awk因此,虽然您可以在(使用<字符串比较运算符)甚至bash使用)中实现自己的排序算法[[ < ]],但对于 中的任意路径bash,可移植,最简单的可能是诉诸于perl

\n\n

有了bash4.4+,你可以这样做:

\n\n
readarray -td \'\' sorted_filearray < <(perl -MFile::Basename -l0 -e \'\n  print for sort {basename($a) cmp basename($b)} @ARGV\' -- "${filearray[@]}")\n
Run Code Online (Sandbox Code Playgroud)\n\n

这给出了strcmp()类似的命令。对于基于语言环境排序规则的顺序(例如 glob 或 的输出)ls,请将-Mlocale参数添加到perl。对于数字排序(更像 GNU sort -g,因为它支持 等数字+31.2e-5而不是千位分隔符,尽管不是十六进制),请使用<=>代替cmp(并且再次-Mlocale像命令一样尊重用户的小数标记sort)。

\n\n

您将受到命令参数最大大小的限制。为了避免这种情况,您可以将文件列表传递到perl其标准输入,而不是通过参数:

\n\n
readarray -td \'\' sorted_filearray < <(\n  printf \'%s\\0\' "${filearray[@]}" | perl -MFile::Basename -0le \'\n    chomp(@files = <STDIN>);\n    print for sort {basename($a) cmp basename($b)} @files\')\n
Run Code Online (Sandbox Code Playgroud)\n\n

对于旧版本的bash,您可以使用while IFS= read -rd \'\'循环代替readarray -d \'\'或 getperl输出正确引用的路径列表,以便您可以将其传递给eval "array=($(perl...))".

\n\n

使用zsh,您可以伪造一个全局扩展,您可以为其定义排序顺序:

\n\n
sorted_filearray=(/(e{\'reply=($filearray)\'}oe{\'REPLY=$REPLY:t\'}))\n
Run Code Online (Sandbox Code Playgroud)\n\n

我们reply=($filearray)实际上强制 glob 扩展(最初只是/)成为数组的元素。然后我们定义基于文件名尾部的排序顺序。

\n\n

对于strcmp()类似于 - 的顺序,请将区域设置固定为 C。对于数字排序(类似于 GNU sort -V,在比较和(例如,在区域设置中,其中是小数点)时不会sort -n产生显着差异),添加glob 限定符。1.41.23.n

\n\n

除了 之外oe{expression},您还可以使用函数来定义排序顺序,例如:

\n\n
by_tail() REPLY=$REPLY:t\n
Run Code Online (Sandbox Code Playgroud)\n\n

或更高级的,例如:

\n\n
by_numbers_in_tail() REPLY=${(j:,:)${(s:,:)${REPLY:t}//[^0-9]/,}}\n
Run Code Online (Sandbox Code Playgroud)\n\n

(因此a/foo2bar3.pdf(2,3 个数字) 排序在b/bar1foo3.pdf(1,3) 之后但在c/baz2zzz10.pdf(2,10) 之前)\n并用作:

\n\n
sorted_filearray=(/(e{\'reply=($filearray)\'}no+by_numbers_in_tail))\n
Run Code Online (Sandbox Code Playgroud)\n\n

当然,这些可以应用于真实的球体,因为这就是它们的主要用途。例如,对于pdf任何目录中的文件列表,按基本名称/尾部排序:

\n\n
pdfs=(**/*.pdf(N.oe+by_tail))\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

\xc2\xb9 如果基于 - 的排序是可接受的,并且对于短字符串,您可以在传递到之前strcmp()将字符串转换为其十六进制编码,并在排序后转换回来。awksort

\n