bash에서 파일을 생성하거나 자르거나 덮어쓸 수 있는지 확인하는 방법은 무엇입니까?

bash에서 파일을 생성하거나 자르거나 덮어쓸 수 있는지 확인하는 방법은 무엇입니까?

foo.sh file.txt사용자는 스크립트의 특정 지점 에서 생성되거나 덮어쓰여지는 파일 경로를 사용하여 내 스크립트를 호출합니다 foo.sh dir/file.txt.

생성 또는 덮어쓰기 동작은 파일을 출력 리디렉션 연산자 오른쪽에 배치 >하거나 인수로 전달하는 요구 사항과 매우 유사합니다 tee(사실 인수로 전달하는 것이 tee바로 제가 수행하는 작업입니다).

스크립트를 시작하기 전에 파일을 제대로 확인하고 싶습니다.할 수 있는실제로 생성하지 않고 생성/덮어쓰기됩니다. 이 검사는 완벽할 필요는 없습니다. 예, 검사와 실제로 파일을 작성하는 시점 사이에 상황이 바뀔 수 있다는 것을 알고 있습니다. 하지만 여기서는 괜찮습니다.최대 노력파일 경로가 잘못된 경우 조기 종료할 수 있도록 솔루션을 입력하세요.

파일을 생성할 수 없는 이유의 예:

  • 파일에 디렉터리 구성 요소가 포함되어 있지만 dir/file.txt디렉터리가 dir존재하지 않습니다.
  • 사용자에게 지정된 디렉터리(또는 디렉터리가 지정되지 않은 경우 CWD)에 대한 쓰기 권한이 없습니다.

예, 권한을 "사전"으로 확인하는 것이 아니라는 것을 알고 있습니다.유닉스 웨이™, 오히려 먼저 수술을 해보고 용서를 구했어야 했는데. 그러나 내 특정 스크립트에서는 이로 인해 사용자 경험이 좋지 않아 담당 구성 요소를 변경할 수 없었습니다.

답변1

명백한 테스트는 다음과 같습니다.

if touch /path/to/file; then
    : it can be created
fi

그러나 아직 파일이 없으면 실제로 파일을 생성합니다. 우리는 그것을 스스로 정리할 수 있습니다:

if touch /path/to/file; then
    rm /path/to/file
fi

하지만 이렇게 하면 원하지 않는 기존 파일이 삭제됩니다.

그러나 우리는 이 문제에 대한 해결책을 가지고 있습니다:

if mkdir /path/to/file; then
    rmdir /path/to/file
fi

디렉터리는 디렉터리의 다른 개체와 동일한 이름을 가질 수 없습니다. 디렉토리를 만들 수는 있지만 파일은 만들 수 없는 상황은 상상할 수 없습니다. 이 테스트가 끝나면 스크립트는 자유롭게 루틴을 생성 /path/to/file하고 원하는 작업을 수행할 수 있습니다.

답변2

내가 모은 것에서 사용시 확인하고 싶은 것

tee -- "$OUT_FILE"

( --그렇지 않으면 -로 시작하는 파일 이름에는 작동하지 않습니다.) tee파일은 쓰기를 위해 성공적으로 열립니다.

그게 다야:

  • 파일 경로의 길이가 PATH_MAX 제한을 초과하지 않습니다.
  • 파일이 존재하고(symlink 확인 후) 유형이 아닙니다.목차그리고 당신은 그것에 대한 쓰기 권한을 가지고 있습니다.
  • 파일이 존재하지 않는 경우 파일의 디렉토리 이름은 디렉토리로 존재하고(심볼릭 링크 확인 후) 사용자에게 이에 대한 쓰기 및 검색 권한이 있으며 파일 이름은 디렉토리가 있는 파일 시스템의 NAME_MAX 제한을 초과하지 않습니다.
  • 또는 파일이 존재하지 않고 심볼릭 링크 순환도 아니지만 위의 조건을 충족하는 파일을 가리키는 심볼릭 링크입니다.

파일 이름에 포함될 수 있는 바이트 값, 디스크 할당량, 프로세스 제한, selinux, apparmor 또는 기타 보안 메커니즘, 전체 파일 시스템, 남은 inode 없음에 대한 제한이 있는 vfat, ntfs 또는 hfsplus와 같은 파일 시스템은 지금은 무시합시다. 장치 어떤 이유로든 이런 방식으로 열 수 없는 파일, 현재 일부 프로세스 주소 공간에 매핑된 실행 파일 등은 모두 파일을 열거나 생성하는 기능에 영향을 줄 수 있습니다.

그리고 zsh:

zmodload zsh/system
tee_would_likely_succeed() {
  local file=$1 ERRNO=0 LC_ALL=C
  if [ -d "$file" ]; then
    return 1 # directory
  elif [ -w "$file" ]; then
    return 0 # writable non-directory
  elif [ -e "$file" ]; then
    return 1 # exists, non-writable
  elif [ "$errnos[ERRNO]" != ENOENT ]; then
    return 1 # only ENOENT error can be recovered
  else
    local dir=$file:P:h base=$file:t
    [ -d "$dir" ] &&    # directory
      [ -w "$dir" ] &&  # writable
      [ -x "$dir" ] &&  # and searchable
      (($#base <= $(getconf -- NAME_MAX "$dir")))
    return
  fi
}

bashBourne과 유사한 쉘 에서 교체하기만 하면 됩니다.

zmodload zsh/system
tee_would_likely_succeed() {
  <zsh-code>
}

그리고:

tee_would_likely_succeed() {
  zsh -s -- "$@" << 'EOF'
  zmodload zsh/system
  <zsh-code>
EOF
}

여기서 구체적인 기능 zsh$ERRNO(마지막 시스템 호출의 오류 코드 노출) 및 $errnos[]해당 표준 C 매크로 이름으로 변환하는 연관 배열입니다. 및 $var:h(csh에서) 및 $var:P(zsh 5.3 이상이 필요함).

Bash에는 아직 동등한 것이 없습니다.

$file:hdir=$(dirname -- "$file"; echo .); dir=${dir%??}, 또는 GNU dirname: 로 대체될 수 있습니다 IFS= read -rd '' dir < <(dirname -z -- "$file").

$errnos[ERRNO] == ENOENT한 가지 접근 방식은 ls -Ld파일을 실행하여 오류 메시지가 ENOENT 오류에 해당하는지 확인하는 것입니다. 그러나 이 작업을 안정적이고 이식 가능하게 수행하는 것은 까다롭습니다.

한 가지 접근 방식은 다음과 같습니다.

msg_for_ENOENT=$(LC_ALL=C ls -d -- '/no such file' 2>&1)
msg_for_ENOENT=${msg_for_ENOENT##*:}

(오류 메시지를 가정마치다syserror()) 그런 다음 대신 다음 ENOENT을 수행하십시오.:[ -e "$file" ]

err=$(ls -Ld -- "$file" 2>&1)

ENOENT 오류를 확인하세요.

case $err in
  (*:"$msg_for_ENOENT") ...
esac

이 부분은 .NET , 특히 FreeBSD에서 $file:P구현하기 가장 까다로운 부분입니다 .bash

realpathFreeBSD에는 명령과 옵션을 readlink허용 하는 명령이 있지만 -f파일이 해결되지 않은 심볼릭 링크인 상황에서는 사용할 수 없습니다. 이는 perls 와 동일합니다 Cwd::realpath().

pythonos.path.realpath()와 유사하게 작동하는 것 같습니다 zsh $file:P. 따라서 적어도 python하나의 버전이 설치되어 있고 그 중 하나를 참조하는 명령(FreeBSD에서는 제공되지 않음)이 있다고 가정 python하면 다음을 수행할 수 있습니다.

dir=$(python -c '
import os, sys
print(os.path.realpath(sys.argv[1]) + ".")' "$dir") || return
dir=${dir%.}

그러나 에서 모든 작업을 수행할 수도 있습니다 python.

또는 이러한 극단적인 경우를 모두 처리하지 않기로 결정할 수도 있습니다.

답변3

고려해야 할 한 가지 옵션은 다음과 같습니다.만들다이전에 파일을 작성하고 나중에 스크립트에서 채우십시오. 명령 을 사용하여 exec파일 설명자(예: 3, 4 등)에서 파일을 연 다음 파일 설명자(예: >&3등)로 리디렉션을 사용하여 파일에 내용을 쓸 수 있습니다.

그것은 다음과 같습니다:

#!/bin/bash

# Open the file for read/write, so it doesn't get
# truncated just yet (to preserve the contents in
# case the initial checks fail.)
exec 3<>dir/file.txt || {
    echo "Error creating dir/file.txt" >&2
    exit 1
}

# Long checks here...
check_ok || {
    echo "Failed checks" >&2
    # cleanup file before bailing out
    rm -f dir/file.txt
    exit 1
}

# We're ready to write, first truncate the file.
# Use "truncate(1)" from coreutils, and pass it
# /dev/fd/3 so the file doesn't need to be reopened.
truncate -s 0 /dev/fd/3

# Now populate the file, use a redirection to write
# to the previously opened file descriptor.
populate_contents >&3

또한 a를 사용하여 trap잘못된 파일을 정리할 수도 있습니다. 이는 일반적인 관행입니다.

이렇게 하면진짜권한을 확인하면 파일을 생성하는 동시에 파일을 조기에 실행할 수 있으므로 실패하더라도 오랜 확인을 위해 기다리느라 시간을 낭비할 필요가 없습니다.


고쳐 쓰다:검사에 실패할 경우 파일 손상을 방지하려면 bash를 사용하세요.fd<>file리디렉션파일을 즉시 자르지 않습니다. (우리는 파일에서 읽는 것에 신경 쓰지 않습니다. 이것은 단지 해결 방법이므로 자르지 않습니다. 를 추가하는 것도 >>가능하지만 이미지에 대한 O_APPEND 플래그를 유지하는 것이 더 우아하다고 생각하는 경향이 있습니다.)

콘텐츠를 바꿀 준비가 되면 먼저 파일을 잘라야 합니다(그렇지 않으면 파일에 이전보다 적은 바이트를 쓰면 뒤따르는 바이트가 그대로 남게 됩니다).자르기(1)/dev/fd/3파일을 다시 열 필요가 없도록 더미 파일을 사용하여 coreutils 명령에 열려 있는 파일 설명자를 전달하면 됩니다 . (기술적으로는 유사하고 간단한 접근 방식이 : >dir/file.txt작동할 수 있지만 파일을 다시 열 필요가 없는 것이 더 우아한 솔루션입니다.)

답변4

test아래에 설명된 일반 명령을 사용하는 것은 어떻습니까?

FILE=$1

DIR=$(dirname $FILE) # $DIR now contains '.' for file names only, 'foo' for 'foo/bar'

if [ -d $DIR ] ; then
  echo "base directory $DIR for file exists"
  if [ -e $FILE ] ; then
    if [ -w $FILE ] ; then
      echo "file exists, is writeable"
    else
      echo "file exists, NOT writeable"
    fi
  elif [ -w $DIR ] ; then
    echo "directory is writeable"
  else
    echo "directory is NOT writeable"
  fi
else
  echo "can NOT create file in non-existent directory $DIR "
fi

관련 정보