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
$0
sh
$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
できるだけ多くの引数を使用して(できるだけ少ない回数)、インラインスクリプトを呼び出す準備をします。各呼び出しでは$0
before に設定され、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
。