web-dev-qa-db-ja.com

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

割り当てについては、関数cp(コピー)と同じ基本機能を持つ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
8
timmaay92

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にする必要がないことに注意してください。 did最初にそれを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"
}

また、ソースが実際に存在することを確認することもできます。これは、cpdoesdo(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- 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にリダイレクトしないでください。さらに、最初のテストでは、文字列fileではなく、位置パラメータ$2を使用する必要があります。

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

ユーザーがコードで「はい」と言った場合、関数はターゲットファイルのファイル名をターゲットファイルに書き込みます。これはソースファイルのコピーではありません。これは、関数の実際のコピービットに該当するはずです。

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

同じことは、以前にtrを呼び出すことで間違っています。 readは変数の値を設定しますが、出力を生成しないので、readを何かにパイプすることは無意味です。

ユーザーが「いいえ」と言わない限り、明示的な終了は必要ありません(または、関数はコードのビットのようにエラー状態に遭遇しますが、関数なので、returnではなくexitを使用します)。

また、「関数」と言いましたが、実装はスクリプトです。

https://www.shellcheck.net/ をご覧ください。これは、シェルスクリプトの問題のあるビットを特定するための優れたツールです。


catの使用は、ファイルの内容をコピーするone方法です。他の方法は次のとおりです

  • dd if="$1" of="$2" 2>/dev/null
  • フィルタのようなユーティリティを使用して、データを通過させるだけです。 sed "" "$1" >"2"またはawk '1' "$1" >"$2"またはtr '.' '.' <"$1" >"$2"など.
  • 等.

トリッキーなビットは、関数にソースからターゲットにメタデータ(所有権と権限)をコピーさせることです。

もう1つの注意点は、ターゲットが/dev/ttyのようなもの(通常でないファイル)の場合、私が作成した関数の動作はcpとはかなり異なります。

26
Kusalananda