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

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

cp課題として、関数(コピー)と基本機能が同じbash関数を巧妙に作成してもらうよう求められました。あるファイルを別のファイルにコピーするだけで、複数のファイルを新しいディレクトリにコピーする必要はありません。

私は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すべての同様のシェルで動作します。

複数のソースファイルをサポートするための追加の調整と、-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ユーザーの小文字/大文字の応答を使用する必要はありませんtrbashとにかくそれをしたい場合は(大文字の場合)または(小文字の場合)を使用する方が安い方法REPLY="${REPLY^^}"ですREPLY="${REPLY,,}"

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

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

以前に私に電話したときも同じ問題がありましたtrread変数の値は設定されますが、出力は生成されないため、read何もパイプで接続することは意味がありません。

ユーザーが「いいえ」と言わない限り、明示的に終了する必要はありません(または私のコードビットに示されているように、関数はいくつかのエラー条件に遭遇しますが、これは私が使用している関数なのでそうではありませんreturnexit

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

見てhttps://www.shellcheck.net/、これはシェルスクリプトの問題のある部分を識別するための素晴らしいツールです。


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

  • dd if="$1" of="$2" 2>/dev/null
  • sed "" "$1" >"2"フィルタなどのユーティリティを使用すると、またはawk '1' "$1" >"$2"などtr '.' '.' <"$1" >"$2"のデータのみを渡すことができます。
  • など。

トリッキーな部分は、ソースからターゲットにメタデータ(所有権と権限)をコピーする機能を持つことです。

cp注目すべきもう一つのことは、ターゲットが/dev/tty(非正規ファイル)のような場合、私が書いた関数がまったく異なる動作をするということです。

関連情報