ファイル名と取り扱いとコマンド置換

ななし 『ファイルがないと * のままになるので、ls DIR 2>/dev/null と書いたりしますね。』 (2007/07/09 11:10)

まぁ、そのとおりなんだけど、ls とコマンド置換を使ってファイル名を取得しようとすると、パスに空白が含まれていたりする場合にはまれる。

% touch 'odz buffer'
% ls `ls`
ls: odz: No such file or directory
ls: buffer: No such file or directory

あと、関係ないけど、-F オプション付きの GNU ls の挙動はなんだかよくわからない。

% ln -s usr .
% ls -F usr
usr@
% ls -F usr/
X11R6/  bin/  doc/  games/  include/  lib/  lib64/  local/  sbin/  share/  src/
% ls -F -H usr
X11R6/  bin/  doc/  games/  include/  lib/  lib64/  local/  sbin/  share/  src/

で、調べてみたら bash/zsh では nullglob オプションなんてのが存在するので、それを使えばいい。
bash のデフォルトでは、glob にマッチするパスが見つからない場合はパス展開が行われず、zsh のデフォルトではエラーになるのだけど、両方ともに nullglob をセットするとで空に展開されるようになる。
bash の場合は

shopt -s nullglob

zsh の場合は

setopt nullglob

とする。
これをつかって、件の for_all を書き換えればこうなる*1

#!/bin/bash

shopt -s nullglob

function rec {
    local dir="$1"
    shift

    for file in "$dir/"*; do
        if [ -f "$file" ]; then 
            "$@" "$file"
        elif [ -d "$file" ]; then 
            rec "$file" "$@"
        fi
    done
}

rec "$@"

ちなみに、

#!/bin/bash

function rec {
    local dir="$1"
    shift

    ls "$dir" | while read file; do
        local path="$dir/$file"
        
        if [ -f "$path" ]; then 
            "$@" "$path"
        elif [ -d "$path" ]; then 
            rec "$path" "$@"
        fi
    done
}

rec "$@"

というネタも思いついたけど、やってみたらパス名に改行を含むことができるらしいので没。

*1:パラメータのとり方があれなのでパラメータの順序を勝手に変更してある