bash で独自の cp 関数を作成する

bash で独自の cp 関数を作成する

課題として、関数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位置パラメータ を使用する必要があります。$2file

私が行ったように を使用するとcase ... esac、 を使用してユーザーからの応答を小文字/大文字にする必要がなくなりますtr。 で、とにかくこれを実行する場合、より安価な方法は、 (大文字化) または(小文字化)bashを使用することです。REPLY="${REPLY^^}"REPLY="${REPLY,,}"

ユーザーが「はい」と答えた場合、コードでは、関数はターゲット ファイルのファイル名をターゲット ファイルに格納します。これはソース ファイルのコピーではありません。関数の実際のコピー ビットにフォールスルーされる必要があります。

コピー部分は、パイプラインを使用して実装したものです。パイプラインは、あるコマンドの出力から別のコマンドの入力にデータを渡すために使用されます。これは、ここで行う必要はありません。catソース ファイルで呼び出し、その出力をターゲット ファイルにリダイレクトするだけです。

先ほどのof の呼び出しでも同じことが起こりますtrread変数の値は設定されますが、出力は生成されないので、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(通常以外のファイル) である場合とはまったく異なる動作をするということです。

関連情報