cp
そのファイルがすでに存在する場合、ユーティリティは喜んで、ユーザーにメッセージを表示せずに、目的のファイルを上書きします。
cp
を使用せずに基本的な機能を実装する関数cp
は、
cp () {
cat "$1" >"$2"
}
ターゲットを上書きする前にユーザーにプロンプトを表示したい場合(非対話型シェルによって関数が呼び出された場合、これを行うことが望ましくない場合があることに注意してください):
cp () {
if [ -e "$2" ]; then
printf '"%s" exists, overwrite (y/n): ' "$2" >&2
read
case "$REPLY" in
n*|N*) return ;;
esac
fi
cat "$1" >"$2"
}
診断メッセージは、標準エラーストリームに送信されます。これは私がやっていることですprintf ... >&2
。
rm
リダイレクトによって切り捨てられるため、実際にはターゲットファイルを必要としないことに注意してください。私たちがした場合でしたしたいrm
まず、あなたはそれがディレクトリだかどうかを確認する必要があると思いますし、それがある場合、代わりにそのディレクトリ内のターゲットファイルを、だけの方法は入れcp
ないでしょう。これはそれをしていますが、まだ明示的ではありませんrm
:
cp () {
target="$2"
if [ -d "$target" ]; then
target="$target/$1"
fi
if [ -d "$target" ]; then
printf '"%s": is a directory\n' "$target" >&2
return 1
fi
if [ -e "$target" ]; then
printf '"%s" exists, overwrite (y/n): ' "$target" >&2
read
case "$REPLY" in
n*|N*) return ;;
esac
fi
cat "$1" >"$target"
}
また、ソースが実際に存在することを確認することもできます。これは、実際にソースが存在することを確認することもcp
できます(cat
そうすることもできるため、完全に省略できますが、そうすると空のターゲットファイルが作成されます)。
cp () {
if [ ! -f "$1" ]; then
printf '"%s": no such file\n' "$1" >&2
return 1
fi
target="$2"
if [ -d "$target" ]; then
target="$target/$1"
fi
if [ -d "$target" ]; then
printf '"%s": is a directory\n' "$target" >&2
return 1
fi
if [ -e "$target" ]; then
printf '"%s" exists, overwrite (y/n): ' "$target" >&2
read
case "$REPLY" in
n*|N*) return ;;
esac
fi
cat "$1" >"$target"
}
この関数は「バシズム」を使用せず、すべてのsh
ようなシェルで動作するはずです。
複数のソースファイルをサポートするためにもう少し調整し-i
、既存のファイルを上書きするときにインタラクティブプロンプトをアクティブにするフラグを追加します。
cp () {
local interactive=0
# Handle the optional -i flag
case "$1" in
-i) interactive=1
shift ;;
esac
# All command line arguments (not -i)
local -a argv=( "$@" )
# The target is at the end of argv, pull it off from there
local target="${argv[-1]}"
unset argv[-1]
# Get the source file names
local -a sources=( "${argv[@]}" )
for source in "${sources[@]}"; do
# Skip source files that do not exist
if [ ! -f "$source" ]; then
printf '"%s": no such file\n' "$source" >&2
continue
fi
local _target="$target"
if [ -d "$_target" ]; then
# Target is a directory, put file inside
_target="$_target/$source"
elif (( ${#sources[@]} > 1 )); then
# More than one source, target needs to be a directory
printf '"%s": not a directory\n' "$target" >&2
return 1
fi
if [ -d "$_target" ]; then
# Target can not be overwritten, is directory
printf '"%s": is a directory\n' "$_target" >&2
continue
fi
if [ "$source" -ef "$_target" ]; then
printf '"%s" and "%s" are the same file\n' "$source" "$_target" >&2
continue
fi
if [ -e "$_target" ] && (( interactive )); then
# Prompt user for overwriting target file
printf '"%s" exists, overwrite (y/n): ' "$_target" >&2
read
case "$REPLY" in
n*|N*) continue ;;
esac
fi
cat -- "$source" >"$_target"
done
}
コードの間隔が不適切ですif [ ... ]
(の前後[
、およびの前にスペースが必要です]
)。また/dev/null
、テスト自体には出力がないので、テストをにリダイレクトしないでください。さらに、最初のテスト$2
では、文字列ではなく位置パラメータを使用する必要がありますfile
。
case ... esac
私のように使用すると、を使用してユーザーからの応答を小文字/大文字にする必要がなくなりますtr
。でbash
、とにかくこれを実行したい場合、より安価な方法は、REPLY="${REPLY^^}"
(大文字)またはREPLY="${REPLY,,}"
(小文字)を使用することでした。
ユーザーがコードで「はい」と言った場合、関数はターゲットファイルのファイル名をターゲットファイルに書き込みます。これはソースファイルのコピーではありません。それは関数の実際のコピービットに陥るはずです。
コピービットは、パイプラインを使用して実装したものです。パイプラインは、あるコマンドの出力から別のコマンドの入力にデータを渡すために使用されます。これは、ここで行う必要があることではありません。cat
ソースファイルを呼び出し、その出力をターゲットファイルにリダイレクトするだけです。
同じことはtr
以前のあなたの呼び出しで間違っています。 read
変数の値を設定しますが、出力を生成しないためread
、何かへのパイプは無意味です。
ユーザーが「いいえ」と言わない限り、明示的な終了は必要ありません(または、関数はコードのビットのようにエラー条件に遭遇しますが、それはreturn
でなく、私が使用する関数なのでexit
)。
また、「関数」と言いましたが、実装はスクリプトです。
https://www.shellcheck.net/を確認してください。これは、シェルスクリプトの問題のあるビットを識別するための優れたツールです。
の使用cat
は、ファイルの内容をコピーする1つの方法にすぎません。他の方法には、
dd if="$1" of="$2" 2>/dev/null
- 任意のフィルタのようなユーティリティ行うことができます使用することは、単にデータを、例えば通過する
sed "" "$1" >"2"
か、awk '1' "$1" >"$2"
またはtr '.' '.' <"$1" >"$2"
など
- 等
トリッキーなのは、関数にメタデータ(所有権とアクセス許可)をソースからターゲットにコピーさせることです。
もう1つ注意すべき点は、私が作成した関数cp
は、ターゲットが/dev/tty
たとえば(非標準ファイル)のようなものである場合とはまったく異なる動作をすることです。