Membuat fungsi cp saya sendiri di bash

8

Untuk tugas saya diminta untuk secara cerdik menulis fungsi bash yang memiliki fungsi dasar yang sama dengan fungsi cp(copy). Hanya perlu menyalin satu file ke yang lain, jadi tidak ada banyak file yang disalin ke direktori baru.

Karena saya baru menggunakan bahasa bash, saya tidak dapat mengerti mengapa program saya tidak berfungsi. Fungsi asli meminta untuk menimpa file jika sudah ada, jadi saya mencoba mengimplementasikannya. Itu gagal.

Tampaknya file gagal di beberapa baris, tetapi yang paling penting pada kondisi di mana ia memeriksa apakah file yang akan disalin sudah ada ( [-e "$2"]). Meski begitu, masih menunjukkan pesan yang seharusnya dipicu jika kondisi itu terpenuhi (Nama file ...).

Adakah yang bisa membantu saya memperbaiki file ini, mungkin memberikan beberapa wawasan yang berguna dalam pemahaman dasar bahasa saya? Kode tersebut adalah sebagai berikut.

#!/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
timmaay92
sumber
17
Mulailah dengan www.shellcheck.net
steeldriver
2
Jika ini ulasan kode: jangan pernah lakukan [ ... ] &> /dev/null. Tes yang didukung oleh [ ... ]selalu diam, itu hanya dalam kasus kesalahan sintaks yang dihasilkan output. Membungkam tes semacam itu hanyalah menggali kubur Anda sendiri.
muru
1
Satu poin: cat $1 | $2"menyalurkan" output dari perintah pertama ke perintah di variabel kedua Anda. Tapi itu adalah nama file, bukan nama perintah untuk dieksekusi.
Floris
Anda perlu meninjau sintaks dasar dari [perintah di if.
Barmar
Dan Anda perlu mempelajari perbedaan antara pemipaan |dan pengalihan ke file dengan >. Ini adalah masalah sintaksis dasar yang seharusnya sudah dibahas di kelas Anda.
Barmar

Jawaban:

26

The cputilitas akan dengan senang hati menimpa file target jika file yang sudah ada, tanpa disuruh pengguna.

Suatu fungsi yang mengimplementasikan cpkemampuan dasar , tanpa menggunakan cpadalah

cp () {
    cat "$1" >"$2"
}

Jika Anda ingin meminta pengguna sebelum menimpa target (perhatikan bahwa mungkin tidak diinginkan untuk melakukan ini jika fungsinya dipanggil oleh shell non-interaktif):

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"
}

Pesan diagnostik harus pergi ke aliran kesalahan standar. Inilah yang saya lakukan printf ... >&2.

Perhatikan bahwa kita tidak benar-benar perlu ke rmfile target karena pengalihan akan memotongnya. Jika kita memang menginginkannya rmterlebih dahulu, maka Anda harus memeriksa apakah itu direktori, dan jika ya, letakkan file target di dalam direktori itu, seperti yang cpakan dilakukan. Ini melakukan itu, tetapi masih tanpa eksplisit 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"
}

Anda juga mungkin ingin memastikan bahwa sumber benar-benar ada, yang merupakan sesuatu yang cp tidak lakukan ( catmelakukannya juga, sehingga dapat dibiarkan keluar sepenuhnya, tentu saja, tapi hal itu akan membuat file target kosong):

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"
}

Fungsi ini tidak menggunakan "bashism" dan harus bekerja di semua shshell.

Dengan sedikit penyesuaian lebih lanjut untuk mendukung banyak file sumber dan -ibendera yang mengaktifkan prompt interaktif saat menimpa file yang ada:

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
}

Kode Anda memiliki spasi buruk if [ ... ](butuh ruang sebelum dan sesudah [, dan sebelum ]). Anda juga tidak boleh mencoba mengarahkan ulang tes /dev/nullkarena tes itu sendiri tidak memiliki output. Tes pertama selanjutnya harus menggunakan parameter posisi $2, bukan string file.

Menggunakan case ... esacseperti yang saya lakukan, Anda menghindari huruf kecil / huruf besar respon dari pengguna yang menggunakan tr. Dalam bash, jika Anda tetap ingin melakukan ini, cara yang lebih murah untuk melakukannya adalah dengan menggunakan REPLY="${REPLY^^}"(untuk huruf besar) atau REPLY="${REPLY,,}"(untuk huruf kecil).

Jika pengguna mengatakan "ya", dengan kode Anda, fungsinya menempatkan nama file file target ke file target. Ini bukan menyalin file sumber. Seharusnya jatuh ke sedikit menyalin fungsi sebenarnya.

Bit penyalinan adalah sesuatu yang telah Anda terapkan menggunakan pipa. Pipeline digunakan untuk meneruskan data dari output dari satu perintah ke input dari perintah lain. Ini bukan sesuatu yang perlu kita lakukan di sini. Cukup memohon catpada file sumber dan mengarahkan hasilnya ke file target.

Hal yang sama salah dengan Anda menelepon trsebelumnya. readakan mengatur nilai variabel, tetapi tidak menghasilkan output, jadi pemipaan readke apa pun tidak masuk akal.

Tidak diperlukan keluar secara eksplisit kecuali jika pengguna mengatakan "tidak" (atau fungsinya menemukan beberapa kondisi kesalahan seperti dalam bit kode saya, tetapi karena ini adalah fungsi yang saya gunakan returndaripada exit).

Juga, Anda mengatakan "berfungsi", tetapi implementasi Anda adalah skrip.

Lihatlah https://www.shellcheck.net/ , ini adalah alat yang baik untuk mengidentifikasi bit skrip shell yang bermasalah.


Menggunakan cathanyalah salah satu cara untuk menyalin konten file. Cara lain termasuk

  • dd if="$1" of="$2" 2>/dev/null
  • Menggunakan utilitas seperti filter apa pun yang dapat dibuat hanya untuk melewatkan data, misalnya sed "" "$1" >"2"atau awk '1' "$1" >"$2"atau lainnya tr '.' '.' <"$1" >"$2".
  • dll.

Agak sulitnya adalah membuat fungsi menyalin metadata (kepemilikan dan izin) dari sumber ke target.

Satu hal yang perlu diperhatikan adalah bahwa fungsi yang saya tulis akan berperilaku sangat berbeda dari cpjika targetnya adalah /dev/ttymisalnya (file non-reguler).

Kusalananda
sumber
untuk menambahkan beberapa presisi interresting tentang cara kerjanya:: cat "$1" >"$2" akan bekerja dalam 2 kali: shell pertama kali melihat > "$2", yang berarti: create-or-clobber (= kosong) file "$ 2", dan mengarahkan stdout perintah ke file itu. Kemudian meluncurkan perintah cat "$1":, dan mengarahkan kembali stdout ke file "$2"(sudah dikosongkan atau dibuat kosong).
Olivier Dulac
Salah satu jawaban terbaik yang pernah saya lihat di sini! Saya belajar tentang -efperbandingan file dan local -a.
Gardenhead
@gardenhead Ya, -efmenerima pemberitahuan jika kedua file tersebut sama (menangani tautan juga) dan local -amembuat variabel array lokal.
Kusalananda
Terima kasih banyak! Jawaban rumit yang sangat bagus, sangat membantu dalam memahami bahasa juga
timmaay92