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


8

割り当てについては、関数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

17
www.shellcheck.netから開始
steeldriver

2
これがコードレビューだった場合:絶対に行わないください[ ... ] &> /dev/null。がサポートするテスト[ ... ]は常にサイレントです。構文エラーが発生した場合にのみ、出力が生成されます。そのようなテストを沈黙させることは、単にあなた自身の墓を掘ることです。
muru、

1
1つのポイント:cat $1 | $2最初のコマンドの出力を2番目の変数のコマンドに「パイプ」します。ただし、これはファイル名であり、実行するコマンドの名前ではありません。
フローリス2017年

[コマンドの基本的な構文を確認する必要がありますif
Barmar

また、を使用したパイピングと|を使用したファイルへのリダイレクトの違いを知る必要があります>。これらは、クラスですでにカバーされているはずの基本的な構文の問題です。
Barmar

回答:


26

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たとえば(非標準ファイル)のようなものである場合とはまったく異なる動作をすることです。


それがどのように機能するかについていくつかの重要な精度を追加するには::cat "$1" >"$2" は2回機能します:シェルは最初に> "$2"を参照します。次に、コマンドを起動し、cat "$1"そのstdoutをファイルにリダイレクトします"$2"(既に空になっているか、空に作成されています)。
Olivier Dulac

ここで見た中で最高の答えの1つです。-efファイル比較との両方について学びましたlocal -a
gardenhead 2017年

@gardenheadはい、-ef2つのファイルが同じである場合は通知を受け取り(リンクも処理します)local -a、ローカル配列変数を作成します。
クサラナンダ

どうもありがとうございます!本当に素晴らしい精巧な答え、言語の理解にも非常に役立ちます
timmaay92
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.