44703


以下のようにフォルダ内のファイル名を5秒おきに列挙するシェルがあります。

[code]

    for file in `ls /home/test/`; do
        echo "${file}"
        sleep 5
    done
    
[/code]

最後のファイル名を表示した後は5秒待機する必要がないので、ループの最後だけは `sleep 5`
を実行したくないのですが、どのように記述すれば良いでしょうか。


CC BY-SA 4.0
2018-06-11T13:58:37.400
44699
2019-11-02T14:40:57.363
2018-06-11T14:09:18.353
19110
3925
question
linux shellscript sh
シェルスクリプトでループの最後を判定する


Bash 特有の機能を使って良ければ、配列を使って書けます。

[code]

    #!/bin/bash
    
    dir="/path/to/target/directory"
    files=("$dir/"*)
    lastIndex=$((${#files[*]} - 1))
    
    for i in $(seq 0 $((lastIndex - 1)))
    do
        echo "${files[$i]}"
        sleep 5
    done
    echo "${files[$lastIndex]}"
    
[/code]

POSIX shell
限定であれば、一旦ファイルの数を数えて、その後最後のファイルかどうかを判定する方法を思いつきました。スクリプトの実行中にファイルの数が変わりうるときには誤作動するのでご注意ください。

[code]

    #!/bin/sh
    
    dir="/path/to/target/directory"
    
    # ファイルの数をカウント
    lastIndex=0
    for name in "$dir/"*
    do
        lastIndex=$((lastIndex + 1))
    done
    
    # 出力
    i=1
    for name in "$dir/"*
    do
        echo "$name"
        if [ "$i" -lt "$lastIndex" ]
        then
            sleep 5
        fi
        i=$((i + 1))
    done
    
[/code]

これらの方法は愚直ですが、それぞれのファイルに対して実行したいコマンドが遅いときでも使えます。コマンドの実行前に `sleep`
を実行する方法は、`echo` のようにコマンドの実行時間が充分短い場合には有効ですが、実行時間が長い場合には使えません。


CC BY-SA 4.0
2018-06-11T14:29:36.807
44702
2018-06-12T07:25:09.030
2018-06-12T07:25:09.030
19110
19110
44699
answer


絡め手ですが、echoの評価前に5秒スリープするようにします。  
最初の1回はスリープしないようにしておけば、最後の1個の終了後  
ループを抜けて終わります。

[code]

    count=0
    for file in `ls .`; do
      #最初の1回はスリープしない。
      if [ $count -ne  0 ]; then
        sleep 5
      fi
      echo ${file}
      ((count++ ))
    done
    
[/code]


CC BY-SA 4.0
2018-06-11T14:43:38.170
44703
2018-06-11T14:43:38.170
10174
44699
answer

[code]

    #!/bin/sh
    
    cd "/home/test/" && set -- *
    while test "${#@}" -gt 0; do
        { test "${flag:-0}" -eq 1 && sleep 5; } || flag=1
        echo "$1"
        shift
    done
    
[/code]

先に結果を変数に格納しておいて、順次表示する形です。


CC BY-SA 4.0
2018-06-11T16:22:55.037
44709
2018-06-12T01:58:41.757
2018-06-12T01:58:41.757
25936
25936
44699
answer


@伽語蓮弥 さんの回答をベースに、手元で動くようにしたのが次です。 POSIX compliant,
スペース入りファイル名称にも対応しています。また、ファイル名称が `hoge\nfuga` のような、 echo
によって特殊処理されるような名称であった場合にもそのまま出力するために、 printf を用いています。

### test.sh

[code]

    ls_with_sleep() {
        set -- *
        while test $# -gt 0; do
            printf '%s\n' "$1"
            shift
            test $# -eq 0 || {
                echo sleeping...
                sleep 1
            }
        done
    }
    
    ls_with_sleep
    
[/code]

### 実行結果

[code]

     % ls -1
    foo
    hoge fuga
    hoge\nfuga
    test.sh
     % ./test.sh 
    foo
    sleeping...
    hoge fuga
    sleeping...
    hoge\nfuga
    sleeping...
    test.sh
    
[/code]


CC BY-SA 4.0
2018-06-12T01:13:03.557
44711
2018-06-12T01:43:23.840
2018-06-12T01:43:23.840
754
754
44699
answer

[code]

    LAST="`ls |tail -1`"
    ls | while read i ;do
      echo $i
      if [ "$i" = "$LAST" ];then
        break
      fi
      sleep 5
    done
    
[/code]


CC BY-SA 4.0
2018-06-12T03:36:56.587
44712
2018-06-12T05:30:51.217
2018-06-12T05:30:51.217
28909
28909
44699
answer


ループの中(`do`)ではなく評価の方(`while`)にやりたいことを持っていけばシンプルに書けると思います。

[code]

    #!/bin/sh
    
    set -- /home/test/*
    while
        [ -e "$1" ] && basename "$1"
        shift
        [ $# -gt 0 ]
    do sleep 5; done
    
[/code]


CC BY-SA 4.0
2019-10-30T12:35:33.417
60129
2019-11-02T14:40:57.363
2019-11-02T14:40:57.363
62
62
44699
answer


別解です。  
ループでCSVの行を作成するとき、行の最後に`,`を付けたくないときの書き方の応用です。

[code]

    #!/bin/bash
    cmd=
    for file in `ls /home/test/`; do
        ${cmd}
        cmd="sleep 5"
        echo "${file}"
    done
    
[/code]


CC BY-SA 4.0
2019-10-31T11:12:51.327
60163
2019-10-31T11:34:34.010
2019-10-31T11:34:34.010
35558
35558
44699
answer