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사용자의 소문자/대문자 응답을 사용하지 않아도 됩니다 tr. bash어쨌든 그렇게 하고 싶다면 (대문자의 경우) 또는 (소문자의 경우)를 사용하는 것이 더 저렴한 방법 REPLY="${REPLY^^}"입니다 REPLY="${REPLY,,}".

사용자가 코드에 대해 "예"라고 대답하면 함수는 대상 파일의 파일 이름을 대상 파일에 넣습니다. 이것은 소스 파일의 복사본이 아닙니다. 함수의 실제 복사 비트에 속해야 합니다.

복사 비트는 파이프를 사용하여 구현하는 것입니다. 파이프는 한 명령의 출력에서 ​​다른 명령의 입력으로 데이터를 전달하는 데 사용됩니다. 우리가 여기서 해야 할 일은 그게 아닙니다. 소스 파일을 호출 cat하고 출력을 대상 파일로 리디렉션하기만 하면 됩니다.

이전에 나에게 전화했을 때도 같은 문제가 있었습니다 tr. read변수의 값은 설정되지만 출력은 생성되지 않으므로 read아무 것도 파이프로 연결하는 것은 의미가 없습니다.

사용자가 "아니요"라고 말하지 않는 한 명시적으로 종료할 필요는 없습니다(또는 내 코드 비트에 표시된 것처럼 함수가 일부 오류 조건을 만나지만 이 함수는 내가 사용하는 함수이기 때문에 그렇지 return않습니다 exit).

또한 "함수"라고 말했지만 구현은 스크립트입니다.

보세요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대상이 (비정규 파일)과 같은 경우 내가 작성한 함수가 완전히 다르게 작동한다는 것입니다.

관련 정보