"한 파일을 다른 파일로 바꾸기" 작업을 설명하는 방법은 여러 가지가 있지만 여기서 중점적으로 설명할 방법은 명령을 사용하여 수행할 수 있는 방법입니다.
mv /that/there/someotherfile /this/here/somefile
이 예에서 /that/there/someotherfile
및 는 /this/here/somefile
현재 파일 시스템에 존재하는 일반 파일이어야 합니다.
모든 것이 순조롭게 진행되면 위 명령을 실행한 후 "이전에 알려진" 파일은 /that/there/someotherfile
사라지지만 해당 내용은 이제 파일의 새 내용이 됩니다 /this/here/somefile
. 후자 이전의 콘텐츠는 "덮어쓰기"됩니다.
이제 "한 디렉토리를 다른 디렉토리로 교체"하는 유사한 작업을 고려하십시오. 예를 들어 /path/to/targetdir
일부 디렉터리를 디렉터리로 덮어씁니다 /some/other/path/to/sourcedir
. 나는 이것을 할 수 있다
rm -rf /path/to/targetdir && mv /some/other/path/to/sourcedir /path/to/targetdir
유효한 "다소 표준" 1 명령을 사용하여 이 작업을 수행 할 수 있습니까?관련된 두 디렉토리의 내용에 관계없이?
나는 그것이 /path/to/targetdir
빈 디렉토리라면,
mv -T /some/other/path/to/sourcedir /path/to/targetdir
...일을 할게요.
/path/to/targetdir
또한 두 디렉터리 아래에 존재하지 않는 상대 경로를 포함하지 않고 두 디렉터리 아래에 존재하는 모든 상대 경로가 동일한 유형의 파일 시스템 항목을 가리킨다는 것을 알고 있습니다 /some/other/path/to/sourcedir
(즉, 모두 디렉터리이거나 일반 파일 등 모두) ), 우리는 다음을 얻을 수 있습니다폐쇄위의 작업에
rsync -a --remove-source-files /some/other/path/to/sourcedir/ /path/to/targetdir
물론rm -rf
위에 주어진 + 시퀀스를 캡슐화하기 위해 2개의 스크립트나 함수를 구현하는 것은 어렵지 않지만 mv
어느 정도 표준 Unix 명령을 통해 이미 사용할 수 있는 것을 구현하는 것을 피하고 싶었습니다.
1 나는 이 질문에 대한 답이 허용된 명령 집합에 대한 개인의 견해에 결정적으로 달려 있다는 것을 알고 있으며 불행히도 여기서는 힘차게 손을 흔드는 것보다 더 나은 것을 제공할 수 없습니다... 예를 들어, 나는 다음 과 cp
같이 생각합니다. mv
덜 표준적"이지만 이 경우에도 이러한 명령이 사용하는 옵션 중 일부는 그렇지 않을 수 있습니다. 실제로 이 조건을 충분히 정확하게 만들면(예: 허용되는 명령을 다음으로 제한)"다음까지 POSIX를 강제 실행"), 위의 의미에서 단일 명령을 사용하여 "한 디렉터리를 다른 디렉터리로 바꾸는" 보편적인 방법은 없을 것입니다. 그렇다면 허용되는 명령 세트를 자유롭게 정의하여 명령을 유용하고 흥미롭게 만드십시오. 즉, 허용되는 명령 집합을 선택할 때 귀하의 좋은 취향을 따를 준비가 되어 있습니다.
2 유명한 마지막 단어.
답변1
rsync
일반적인 재부팅 가능성 을 잊어버린 경우 이 매우 위험한 명령이 작동할 수 있습니다.
rsync -av --remove-source-files --delete-before /path/to/source/ /path/to/target
하지만 몇 가지 주의사항이 있습니다.
- 파일을 이동하는 것이 아니라 파일을 복사하고 원본 파일을 삭제합니다. 이는 동일한 파일 시스템에 있는 대용량 파일의 경우 문제가 될 수 있습니다.
- 소스 디렉터리는 삭제되지 않습니다. 이것이 문제인 경우 두 개의 명령으로 돌아가서 원래
rm && mv
구성 으로 되돌릴 수도 있습니다. - 이것은 POSIX가 아닙니다
전반적으로 나는 이 rm && mv
접근 방식을 선호한다고 생각합니다. 이 버전에는 bash
(또는 배열이 있는 다른 쉘)이 필요합니다. 나는 그것이 사용되었다고 생각하며 둘 mv
다 POSIX를 준수합니다.rm
find
rmmv() {
local args=("$@") target
if [ $# -eq 0 ]
then
echo "${0##*/}: missing file operand" >&2
exit 1
elif [ $# -eq 1 ]
then
echo "${0##*/}: missing file operand after '${args[0]}'" >&2
exit 1
fi
target="${args[@]: -1}"
unset "args[${#args[@]}-1]"
if [ -d "$target" ]
then
# Directory target; remove its contents
( cd -P -- "$target" && find . -depth -path './*' -exec rm -rf {} + )
elif [ "${#args[@]}" -gt 1 ]
then
# Multiple sources but not a directory
echo "${0##*/}: target '$target' is not a directory" >&2
exit 2
fi
# Do it
mv -- "${args[@]}" "$target"
}
답변2
TL,DR: 심볼릭 링크를 사용하세요.
POSIX 디렉토리
비어 있지 않은 디렉토리의 자동 교체는 POSIX 시스템 호출만으로는 불가능합니다. 이것rename
시스템 호출은 대상이 빈 디렉터리만 허용합니다. 이것link
시스템 호출은 디렉토리를 대상으로 하는 것이 금지되어 있습니다.
/some/other/path/to/sourcedir
대안으로 /path/to/targetdir
몇 가지 비원자적 방법이 있습니다. 이 답변에서는 대상 디렉터리가 이미 존재한다고 가정합니다. 그렇지 않은 경우 이 경우를 별도로 처리해야 할 수 있으며 이는 동시 상황에서 약간 까다로울 수 있습니다. 또한 소스와 대상이 동일한 파일 시스템에 있다고 가정합니다.
기존 대상을 방해가 되지 않는 곳으로 옮기고 새 버전을 제자리로 옮깁니다. 이로 인해 대상이 존재하지 않는 작은 시간 창이 남게 됩니다. 두 개의 교체 프로세스가 동시에 작동하는 경우 이 방법은 안정적으로 작동하지 않습니다.
mv -f /path/to/targetdir /path/to/targetdir.old mv /some/other/path/to/sourcedir /path/to/targetdir rm -rf /path/to/targetdir.old
기존 대상을 삭제하고 새 버전을 제자리로 이동합니다. 이로 인해 대상이 부분적으로만 채워지는 동안 잠재적으로 큰 시간 창이 남고, 대상이 없는 동안에는 더 작은 시간 창이 남습니다. 두 개의 교체 프로세스가 동시에 작동하는 경우 이 방법은 안정적으로 작동하지 않습니다.
rm -rf /path/to/targetdir mv /some/other/path/to/sourcedir /path/to/targetdir
기존 대상을 지우고 새 버전을 제자리로 옮깁니다. 이로 인해 대상이 부분적으로만 채워지는 동안 잠재적으로 긴 시간이 남게 됩니다. 대상 창은 유지되고 원자적으로 채워집니다. 두 개의 교체 프로세스가 동시에 작동하는 경우 이 방법은 안정적으로 작동하지 않습니다.
(cd /path/to/targetdir && rm -rf ..?* .[!.]* *) mv /some/other/path/to/sourcedir /path/to/tmp/targetdir mv /path/to/tmp/targetdir /path/to/
새 버전이 대상과 동일한 기본 이름을 갖는 중간 단계를 참고하세요. 이는 필수입니다
mv
. 전달된 대상이mv
기존 디렉터리 인 경우mv
소스가 해당 디렉터리로 이동됩니다. 따라서mv
덮어쓰려는 기존 디렉터리의 상위 디렉터리 로 전달된 대상을 설정해야 합니다 .
커널별 시스템 호출을 사용하려는 경우 원자적으로 교체를 수행할 수 있는 방법이 있을 수 있습니다.
리눅스 디렉토리
시스템 호출renameat2
Linux ≥3.15에서는 모든 유형의 두 디렉터리 항목을 원자적으로 교환할 수 있습니다. 인터페이스를 제공하는 유틸리티는 없습니다.
perl -we '
require "syscall.ph";
sub AT_FDCWD() {return -100}
sub RENAME_EXCHANGE () {return 2}
my ($source, $target) = @ARGV;
$! = 0;
syscall(SYS_renameat2(), AT_FDCWD, $source, AT_FDCWD, $target, 2) != -1 or die $!}
' /some/other/path/to/sourcedir /path/to/targetdir &&
rm -rf /some/other/path/to/sourcedir # This now contains the former target
이것은 완전히 원자적인 대체입니다. 여러 교체 프로세스가 동시에 수행되면 최종 결과는 새 버전 중 하나가 배치되고 다른 버전은 제거되는 것입니다.
디렉토리를 원자적으로 효율적으로 교체하는 또 다른 방법은 다음과 같습니다.바인드 마운트. 이 기능은 많은 Unix 변형에 존재하지만 사용되는 방식은 다를 수 있습니다. 여기서는 mount --bind
루트 권한이 필요한 Linux를 사용하겠습니다 . 또 다른 주목할만한 방법은 bindfs
비정상적인 권한이 필요하지 않은 FUSE 파일 시스템입니다 .
mkdir /path/to/targetdir.old
mount --bind /path/to/targetdir /path/to/targetdir.old
mount --bind/some/other/path/to/sourcedir /path/to/targetdir
rm -rf /path/to/targetdir.old
이렇게 하면 파일이 이동되지 않고 대상 경로 아래에 표시될 뿐입니다. 이 접근 방식을 사용하면 소스가 그대로 유지됩니다.
POSIX 심볼릭 링크
원자 교체를 수행하는 방법은 기호 링크를 사용하는 것입니다. 심볼릭 링크할 수 있는원자로 대체되었습니다. 이는 직관에 어긋나기 때문에 약간 까다롭습니다.symlink
하지 않을 것입니다. 당신은 사용해야합니다rename
. 게다가 만약에mv
디렉토리에 대한 심볼릭 링크인 대상이 주어지면 소스를 이동합니다.아래에심볼릭 링크를 덮어쓰는 대신 디렉토리. mv
심볼릭 링크가 포함된 디렉터리를 대상으로 전달하여 디렉터리 심볼릭 링크인 대상을 재정의 할 수 있습니다 . 이렇게 하려면 소스의 기본 이름이 동일해야 합니다.
# Precondition: /path/to/targetdir doesn't exist or is a symbolic link.
mkdir -p staging
ln -s /some/other/path/to/sourcedir staging/targetdir
old_target=$(cd /path/to/targetdir && pwd -P)
mv staging/targetdir /path/to/targetdir
# Postcondition: /path/to/targetdir is a symbolic link to the new version.
rm -rf "$old_target"
이름 바꾸기는 원자성이므로 동시 인스턴스가 여러 개인 경우 안정적으로 작동하지 않지만 삭제할 항목을 파악하는 것은 아닙니다.
답변3
이 경우 명령을 제거하는 대신 명령을 추가하는 것이 좋습니다.
mv target somewhere_else
mv source target
소스, 대상 및 기타 위치가 모두 동일한 파일 시스템에 있는 경우 두 명령 모두 빠르게 반환되어야 합니다. 그렇다면 반드시 당장은 아닐 수도 있습니다.
rm -rf somewhere_else
디스크 공간이 문제인 경우 위 작업을 하위 트리로 나눌 수 있습니다.
source
일반적으로 다음과 같이 다양한 파일/폴더의 유형과 수에 따라 더 나은 솔루션을 찾을 수 있습니다 target
: rsync
, 재귀 diff/patch
, 버전 제어 등