"sh -c"のインラインシェルスクリプトは期待どおりに実行されませんか?

findディレクトリからコマンドを実行し、見つかったエントリに基づいて出力ファイルを生成しようとしています。

これが私が今まで持っているものです:

docker2:~/src/docker# cat test.sh
#!/bin/sh

find /data -type d -mindepth 1 -maxdepth 1 \( ! -name "." \) -exec sh -c "\
     echo \"Full path: {}\"; echo \"Basename: `basename {}`\"
" \;

ただし、このコマンドの出力は次のとおりです。

Full path: /data/nc
Basename: /data/nc
Full path: /data/src
Basename: /data/src
Full path: /data/sql0
Basename: /data/sql0
Full path: /data/proxy
Basename: /data/proxy

バックティックが機能する必要があることを知っています。

docker2:~/src/docker# cat test.sh
#!/bin/sh

find /data -type d -mindepth 1 -maxdepth 1 \( ! -name "." \) -exec sh -c "\
     echo \"Date: `date`\"; echo \"Full path: {}\"; echo \"Basename: `basename {}`\"
" \;

以下を提供します。

Date: Wed Dec 18 22:56:32 UTC 2019
Full path: /data/nc
Basename: /data/nc
Date: Wed Dec 18 22:56:32 UTC 2019
Full path: /data/src
Basename: /data/src
...

そして変数にも問題があります。

#!/bin/sh

find /data -type d -mindepth 1 -maxdepth 1 \( ! -name "." \) -exec sh -c "\
     export THIS_DIR={}; THIS_DIRECTORY={}; echo \"$THIS_DIR $THIS_DIRECTORY\";
" \;

このコマンドの出力は空で、各ディレクトリに空白行があります/data。つまり、エクスポート時にも変数は保存されません。

私は何が間違っていましたか?私はこれが奇妙な入れ子や作業の順序、または奇妙なシェルコードに関連していると思います。

ベストアンサー1

これは`basename {}`、コンテンツが二重引用符で囲まれているために予想される現象です。これにより、呼び出す前に実行され、find文字列置換コマンドで置き換えられます{}。これは、変数が実際に設定される前に拡張される後の例でも問題になります。

を使用するときにsh -c引数として使用されるスクリプトは、-c実際には常に一重引用符で囲む必要があります。これにより、スクリプト内での引用がより簡単になります。インラインスクリプトのすべての引数は、コードに挿入されたテキストではなくコマンドラインから渡されなければなりません("「find -exec sh -c」を使っても安全ですか?「理由で)。あなたの場合:

#!/bin/sh

find /data -type d -mindepth 1 -maxdepth 1 -exec sh -c '
    echo "Full path: $1"
    echo "Basename: $(basename "$1")"' sh {} \;

または、いくつかの変数を使用してを使用するように切り替えますprintf

#!/bin/sh

find /data -type d -mindepth 1 -maxdepth 1 -exec sh -c '
    pathname=$1
    filename=$(basename "$pathname")
    printf "Full path: %s\n" "$pathname"
    printf "Basename: %s\n" "$filename"' sh {} \;

以下を使用してインラインスクリプトをsh -c実行すると、これは通常シェルまたはスクリプトの名前なので、その文字列を渡してから見つかったパス名を渡します。$0$1$2$0sh$1

または、より効率的にできるだけ少ない数の呼び出しを使用し、sh -cパラメータ置換を使用します。

#!/bin/sh

find /data -type d -mindepth 1 -maxdepth 1 -exec sh -c '
    for pathname do
        printf "Full path: %s\n" "$pathname"
        printf "Basename: %s\n" "${pathname##*/}"
    done' sh {} +

ここでは、findできるだけ多くの引数を使用して(できるだけ少ない回数)、インラインスクリプトを呼び出す準備をします。各呼び出しでは$0before に設定され、sh他の位置パラメータは見つかったパス名からその値を取得します。ループは渡されたパス名($0位置引数ではないため繰り返されません)を繰り返します。

あなたのテストはディレクトリ自体を見つけるのを避ける! -name .ことを前提としています。/dataすでにスキップしているので必要ありません-mindepth 1/dataパスは検索パスの最上位であるため、「深さ0」にあります)。また、findエントリはディレクトリから返されないため、.エントリを使用していない場合、テストはそのエントリを削除しません。この場合を使用できます。/data-mindepth 1! -path /data

単一のディレクトリ内のディレクトリを繰り返していて、次のものを使用したからです。タグやタイムスタンプなどの素晴らしいテストを実行するのに慣れていないので、基本的なfindシェルループを実行できます。

#!/bin/bash

shopt -s dotglob nullglob

for pathname in /data/*; do
    if [[ -d $pathname ]] && [[ ! -L $pathname ]]; then
        printf 'Full path: %s\n' "$pathname"
        printf 'Basename: %s\n' "${pathname##*/}"
    fi
done

ここでは、パス名と名前に関する情報を印刷する前に、その/data名前がディレクトリかどうかを明示的にテストします。のシェルdotglobオプションは、隠された名前に対するワイルドカードパターンの一致をbash許可し、空の*場合やパターンが何も一致しない場合にnullglobループフォームが実行されるのを完全に防ぎます。/data

望むより」「find」の-execオプションについてfind「と一緒に使用する方法に関する一般情報です-exec

おすすめ記事