【问题标题】:Randomizing arg order for a bash for statement为 bash for 语句随机化 arg 顺序
【发布时间】:2009-12-17 21:26:15
【问题描述】:

我有一个 bash 脚本,它使用类似循环的方式处理目录中的所有文件

对于 *.txt 中的 i 做 操作...... 完成

有数千个文件,由于“*.txt”扩展,它们总是按字母数字顺序处理。

有没有一种简单的方法来随机排序并仍然确保我只处理所有文件一次?

【问题讨论】:

    标签: bash


    【解决方案1】:

    假设文件名没有空格,只需替换 List::Util::shuffle 的输出即可。

    for i in `perl -MList::Util=shuffle -e'$,=$";print shuffle<*.txt>'`; do
        ....
    done
    

    如果文件名包含空格但没有嵌入换行符或反斜杠,则一次读取一行。

    perl -MList::Util=shuffle -le'$,=$\;print shuffle<*.txt>' | while read i; do
        ....
    done
    

    为了在 Bash 中完全安全,请使用以 NUL 结尾的字符串。

    perl -MList::Util=shuffle -0 -le'$,=$\;print shuffle<*.txt>' |
    while read -r -d '' i; do
        ....
    done
    

    效率不是很高,但如果需要,可以在纯 Bash 中执行此操作。 sort -R 在内部做了类似的事情。

    declare -a a                     # create an integer-indexed associative array
    for i in *.txt; do
        j=$RANDOM                    # find an unused slot
        while [[ -n ${a[$j]} ]]; do
            j=$RANDOM
        done
        a[$j]=$i                     # fill that slot
    done
    for i in "${a[@]}"; do           # iterate in index order (which is random)
        ....
    done
    

    或者使用传统的 Fisher-Yates 洗牌。

    a=(*.txt)
    for ((i=${#a[*]}; i>1; i--)); do
        j=$[RANDOM%i]
        tmp=${a[$j]}
        a[$j]=${a[$[i-1]]}
        a[$[i-1]]=$tmp
    done
    for i in "${a[@]}"; do
        ....
    done
    

    【讨论】:

    • 数组下标变量或数组下标表达式不必使用美元符号:a[j]=${a[i-1]}man bash 还说“旧格式 $[expression] 已弃用,将在即将发布的 bash 版本中删除。” (支持$(()),例如在你的j=$[RANDOM%i]
    【解决方案2】:

    你可以通过排序命令来管道你的文件名:

    ls | sort --random-sort | xargs ....
    

    【讨论】:

    • 您使用的是哪个排序命令? /bin/sort 没有这样的选项
    • 我正在使用sort (GNU coreutils) 6.10
    • GNU coreutils sort 有它,尽管有些版本存在错误,其有效性因语言环境而异。
    • @tangens 您应该在答案中指定。并非所有的 unix/linux 发行版都带有 gnu 工具集。
    • “排序”实用程序有一个随机选项有点有趣!
    【解决方案3】:

    这是一个依赖于 awk 中非常基本的功能的答案,因此它应该可以在 unice 之间移植。

    ls -1 | awk '{print rand()*100, $0}' | sort -n | awk '{print $2}'
    

    编辑:

    ephemient 提出了一个很好的观点,即上述内容不是空间安全的。这是一个版本:

    ls -1 | awk '{print rand()*100, $0}' | sort -n | sed 's/[0-9\.]* //'
    

    【讨论】:

    • 如果任何文件名包含空格,则中断。
    • 希望没有人创建它们,但文件名中嵌入的换行符仍然会出错。
    【解决方案4】:

    如果你有 GNU coreutils,你可以使用shuf:

    while read -d '' f
    do
        # some stuff with $f
    done < <(shuf -ze *)
    

    这适用于名称中包含空格或换行符的文件。

    题外话编辑:

    在评论中说明SiegeX的观点:

    $ a=42; echo "Don't Panic" | while read line; do echo $line; echo $a; a=0; echo $a; done; echo $a
    Don't Panic
    42
    0
    42
    $ a=42; while read line; do echo $line; echo $a; a=0; echo $a; done < <(echo "Don't Panic"); echo $a
    Don't Panic
    42
    0
    0
    

    管道导致while 在子shell 中执行,因此子shell 中变量的更改不会流回父shell。

    【讨论】:

    • 更具体地说,coreutils≥6.1,我相信。就个人而言,我更喜欢shuf -ze * | while read 而不是done &lt; &lt;(shuf -ze *),但实际上它们是相同的。
    • 我喜欢done &lt; &lt;() 的原因是它与done &lt; filename 相似(避免不必要地使用cat)。
    • @ephemient 进程替换方式&lt; &lt;(foo) 的另一个好处是它不会像管道方法那样创建子shell。
    【解决方案5】:

    这是一个使用标准 unix 命令的解决方案:

    for i in $(ls); do echo $RANDOM-$i; done | sort | cut -d- -f 2-
    

    【讨论】:

    • lssortcut 不是纯 Bash 命令。在包含嵌入换行符的文件名的可怕情况下也会失败。
    • 查看我对几个纯 Bash shuffle 的更新答案,它们偶尔可以毫无问题地处理奇怪的文件名。
    • @ephemient:感谢您提供的解决方案。我以前不知道 Fisher-Yates 洗牌。
    【解决方案6】:

    这是一个 Python 解决方案,如果它在您的系统上可用的话

    import glob
    import random
    files = glob.glob("*.txt")
    if files:
        for file in random.shuffle(files):
            print file
    

    【讨论】:

    • 为了满足原始问题,这需要if file.endswith('.txt')。或者你可以把它变成更通用的东西,比如shuf...
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-04
    • 2023-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多