![bash で独自の cp 関数を作成する](https://rvso.com/image/112012/bash%20%E3%81%A7%E7%8B%AC%E8%87%AA%E3%81%AE%20cp%20%E9%96%A2%E6%95%B0%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B.png)
課題として、関数cp
(copy) と同じ基本機能を持つ bash 関数を巧みに記述するように求められています。1 つのファイルを別のファイルにコピーするだけでよいので、複数のファイルを新しいディレクトリにコピーする必要はありません。
私は bash 言語を初めて使用するので、プログラムが動作しない理由がわかりません。元の関数は、ファイルがすでに存在する場合にファイルを上書きするように要求するので、それを実装しようとしました。失敗します。
ファイルは複数の行で失敗しているようですが、最も重要なのは、コピー先のファイルがすでに存在するかどうかを確認する条件 ( [-e "$2"]
) です。それでも、その条件が満たされた場合にトリガーされるはずのメッセージ (ファイル名...) が表示されます。
このファイルの修正を手伝って、言語の基本的な理解に役立つ洞察を提供してくれる人はいませんか? コードは次のとおりです。
#!/bin/sh
echo "file variable: $2"
if [-e file]&> /dev/null
then
echo "The file name already exists, want to overwrite? (yes/no)"
read | tr "[A-Z]" "[a-z]"
if [$REPLY -eq "yes"] ; then
rm "$2"
echo $2 > "$2"
exit 0
else
exit 1
fi
else
cat $1 | $2
exit 0
fi
答え1
対象ファイルがすでに存在する場合、ユーティリティ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
するdo (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"
}
この関数は「bashisms」を使用せず、すべてのsh
-like シェルで動作するはずです。
-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
ソース ファイルで呼び出し、その出力をターゲット ファイルにリダイレクトするだけです。
先ほどのof の呼び出しでも同じことが起こります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
(通常以外のファイル) である場合とはまったく異なる動作をするということです。