按顺序遍历文件名列表,以便在bash中创建

时间:2014-08-29 22:29:14

标签: bash

解析ls的输出以遍历文件列表bad。那么我应该如何按照它们首次创建的顺序迭代文件列表?我在这里浏览了几个问题,他们似乎都在解析ls

嵌入式链接表明:

  

如果你想要一些特定的排序,事情变得更加困难   只有ls可以执行,例如按mtime排序。如果你想要最老的或   目录中的最新文件,请勿使用ls -t | head -1 - 阅读Bash常见问题解答   99代替。 如果您确实需要目录中所有文件的列表   按顺序由mtime按顺序处理它们,切换到   perl,让你的perl程序打开自己的目录   排序。然后在perl程序中进行处理,或者 - 最坏的情况   场景 - 让perl程序用NUL吐出文件名   分隔符。

     

更好的是,将修改时间放在YYYYMMDD的文件名中   格式,所以glob顺序也是mtime顺序。那你就不需要了   或perl或任何东西。 (绝大多数人都想要的情况   目录中最旧或最新的文件可以通过执行来解决   此。)

这是否意味着在bash中有没有原生方式吗?我没有权限修改文件名以包含时间。我需要在cron中安排一个每5分钟运行一次的脚本,生成一个数组,其中包含按创建时间排序的特定目录中的所有文件,并对文件名执行一些操作并将它们移动到另一个位置。

以下工作但只是因为我没有有趣的文件名。这些文件是由服务器创建的,因此它永远不会有特殊字符,空格,换行符等。

files=( $(ls -1tr) ) 

我可以编写一个perl脚本来完成我需要的操作,但如果有人能在bash中建议正确的方法,我将不胜感激。便携式选项会很棒,但使用最新GNU实用程序的解决方案也不会成为问题。

8 个答案:

答案 0 :(得分:6)

sorthelper=();
for file in *; do
    # We need something that can easily be sorted.
    # Here, we use "<date><filename>".
    # Note that this works with any special characters in filenames

    sorthelper+=("$(stat -n -f "%Sm%N" -t "%Y%m%d%H%M%S" -- "$file")"); # Mac OS X only
    # or
    sorthelper+=("$(stat --printf "%Y    %n" -- "$file")"); # Linux only
done;

sorted=();
while read -d $'\0' elem; do
    # this strips away the first 14 characters (<date>) 
    sorted+=("${elem:14}");
done < <(printf '%s\0' "${sorthelper[@]}" | sort -z)

for file in "${sorted[@]}"; do
    # do your stuff...
    echo "$file";
done;

除了sortstat之外,所有命令都是实际的本机Bash命令(内置)*。如果你真的想要,你可以implement your own sort using Bash builtins only,但我认为没办法摆脱stat

重要的部分是read -d $'\0'printf '%s\0'sort -z。所有这些命令都与其null分隔符选项一起使用,这意味着可以安全地处理任何文件名。此外,在"$file""${anarray[*]}"中使用双引号也很重要。

* 许多人认为GNU工具在某种程度上是Bash的一部分,但从技术上讲他们并非如此。因此,statsortperl一样非原生。

答案 1 :(得分:2)

您可以尝试使用stat命令管道sort

stat -c '%Y %n' * | sort -t ' ' -nk1 | cut -d ' ' -f2-

更新:要使用换行符处理文件名,我们可以在%N中使用stat格式,而不是cut我们可以使用awk这样:

LANG=C stat -c '%Y^A%N' *| sort -t '^A' -nk1| awk -F '^A' '{print substr($2,2,length($2)-2)}'
  1. 需要使用LANG=C以确保stat仅在引用文件名时使用单引号。
  2. ^A使用 Control V A 键一起输入conrtrol-A字符。

答案 2 :(得分:2)

所有警告和警告反对使用ls解析目录,尽管如此,我们都发现自己处于这种情况。如果您确实发现自己需要排序目录输入,那么ls最简洁地用于提供循环是ls -opts | read -r name; do...这将处理文件名中的空格等。无需重置{{1}由于IFS本身的性质。例如:

read

所以要寻找避免使用ls -1rt | while read -r fname; do # where '1' is ONE not little 'L' 的清洁解决方案,但如果推动推进,ls可以谨慎使用,不会让天空掉落或者龙会拔出你的眼睛。

让我添加免责声明,让每个人都满意。如果您喜欢文件名中的ls -opts,那么不要使用newlines来填充循环。如果您的文件名中没有ls,则没有其他不良副作用。

反对: TLDP Bash Howto Intro

newlines

SO用户似乎不知道 contra 的用途是什么 - 请在downvoting之前查阅。

答案 3 :(得分:1)

GNU find + sed + sort的解决方案怎么样?

只要文件名中没有换行符,就可以使用:

find . -type f -printf '%T@ %p\n' | sort -k 1nr | sed 's/^[^ ]* //'

答案 4 :(得分:1)

每个文件都有三个时间戳:

  1. 访问时间:文件已打开并已读取。也称为 atime
  2. 修改时间:文件已写入。也称为 mtime
  3. Inode修改时间:文件的状态已更改,例如文件创建了新的硬链接,或者删除了现有的硬链接;或者如果文件的权限是chmoded或其他一些东西。也称为 ctime
  4. 两者都不代表文件创建的时间,该信息不会保存在任何地方。在文件创建时,所有三个时间戳都被初始化,然后在读取或写入文件时,或者文件的权限被编码,或者创建或销毁硬链接时,每个时间戳都会得到适当的更新。

    因此,您无法根据文件创建时间真正列出文件,因为文件创建时间不会保存在任何地方。最接近的匹配是inode修改时间。

    有关如何在atime中列出文件的详细信息,请参阅ls(1) man page-t-u-c-r选项的说明,mtime或ctime命令。

答案 5 :(得分:1)

确保安装它可能需要做多一些工作(虽然可能已经安装了),但使用zsh代替bash这个脚本很有意义。文件名通配功能更丰富,同时仍然使用类似sh的语言。

files=( *(oc) )

将创建一个数组,其条目是当前目录中的所有文件名,但按更改时间排序。 (使用大写O来反转排序顺序)。这将包括目录,但您可以将匹配限制为常规文件(类似于-type f谓词到find):

files=( *(.oc) )
find脚本中需要

zsh,因为它的大多数用途都包含在各种可用的glob标志和限定符中。

答案 6 :(得分:1)

我刚刚找到了使用bashls(GNU)的方法。
假设您要遍历按修改时间-t)排序的文件名:

while read -r fname; do
    fname=${fname:1:((${#fname}-2))} # remove the leading and trailing "
    fname=${fname//\\\"/\"}          # removed the \ before any embedded "
    fname=$(echo -e "$fname")        # interpret the escaped characters
    file "$fname"                    # replace (YOU) `file` with anything
done < <(ls -At --quoting-style=c)

解释

鉴于某些带有特殊字符的文件名,这是ls输出:

$ ls -A
 filename with spaces   .hidden_filename  filename?with_a_tab  filename?with_a_newline  filename_"with_double_quotes"

$ ls -At --quoting-style=c
".hidden_filename"  " filename with spaces "  "filename_\"with_double_quotes\""  "filename\nwith_a_newline"  "filename\twith_a_tab"

所以你必须处理一些文件名以获得实际的文件名。回顾:

${fname:1:((${#fname}-2))} # remove the leading and trailing "
# ".hidden_filename" -> .hidden_filename
${fname//\\\"/\"}          # removed the \ before any embedded "
# filename_\"with_double_quotes\" -> filename_"with_double_quotes"
$(echo -e "$fname")        # interpret the escaped characters
# filename\twith_a_tab -> filename     with_a_tab

实施例

$ ./script.sh
.hidden_filename: empty
 filename with spaces : empty
filename_"with_double_quotes": empty
filename
with_a_newline: empty
filename    with_a_tab: empty

如图所示,file(或您想要的命令)可以很好地解释每个文件名。

答案 7 :(得分:0)

这是使用stat和关联数组的方法。

n=0
declare -A arr
for file in *; do
    # modified=$(stat -f "%m" "$file") # For use with BSD/OS X
    modified=$(stat -c "%Y" "$file") # For use with GNU/Linux
    # Ensure stat timestamp is unique
    if [[ $modified == *"${!arr[@]}"* ]]; then
        modified=${modified}.$n
        ((n++))
    fi
    arr[$modified]="$file"
done
files=()
for index in $(IFS=$'\n'; echo "${!arr[*]}" | sort -n); do
    files+=("${arr[$index]}")
done

由于sort对行进行排序,$(IFS=$'\n'; echo "${!arr[*]}" | sort -n)确保通过将子shell中的字段分隔符设置为换行符来对关联数组的索引进行排序。

arr[$modified]="${file}"files+=("${arr[$index]}")的引用可确保保留带有新内容等警告的文件名。