나는 이 명령을 사용하여 하드 드라이브를 백업하는 데 필요한 작업을 자동화하도록 Bash에서 스크립트를 작성하고 있습니다 rsync
. 이 구현에서는 다음 단계를 완료하기 위한 스크립트를 설정했습니다.
- Linux 배포판에 따라 백업 마운트를 위한 기본 디렉터리를 결정합니다.
- rsync가 설치되어 있는지 확인하고, 그렇지 않은 경우 사용자에게 설치하라는 메시지를 표시합니다.
- 사용자에게 기본 디렉터리에 올바르게 설치된 드라이브를 선택하라는 메시지를 표시합니다.
- 올바른 디렉터리에 백업 파일을 생성합니다. 여기서 백업 디렉터리 제목은 다음과 같습니다.
YYYY-MM-DD:H:M:S
- 백업용으로 사용
rsync
- 백업 디렉터리가 4개 이상 있는지 확인하고, 그렇다면 가장 오래된 디렉터리를 삭제하세요.
완전성을 위해 while 스크립트를 게시하고 있지만 문제는 165번째 줄에서 발생합니다. 스크립트는 내 디렉터리에 저장되며 명령을 usr/local/bin
사용하거나 사용하지 않고 실행할 수 있습니다 . sudo
명령 없이 실행되는 경우 sudo
(예: backup
) 스크립트에서 호출되는 지점에 도달할 때마다 sudo
사용자에게 비밀번호를 묻는 메시지가 표시되고 올바르게 실행됩니다. 그러나 사용자가 암호를 묻는 스크립트를 기다리며 컴퓨터 앞에 앉아 있을 필요가 없도록 sudo
파일(예: )을 미리 실행하고 싶습니다 . sudo backup
차라리 비밀번호를 한 번만 입력하면 추가 사용자 입력 없이 전체 스크립트가 실행되기를 바랍니다. 불행하게도 이 작업을 수행하면 백업 드라이브에 백업 디렉터리를 만드는 대신 해당 /run/media/username/YYYY-MM-DD:H:M:S
디렉터리에 디렉터리가 생성됩니다. 스크립트를 호출하지 않을 때는 작동하지만 호출할 때는 작동하지 않는 이유를 누가 말해 줄 수 있습니까?media
/run/media/YYYY-MM-DD:H:M:S
sudo
sudo
전방 참조는 /usr/local/bin
my에 있으므로 대체하거나 대체하여 PATH
스크립트를 호출 할 수 있습니다.backup
./backup
bash backup
#!/usr/bin/bash
# backup file
# ================================================================================
# ================================================================================
# - Purpose: This file contains scripts that will create a user defined number
# of backup snapshots, where each snapshot is a full backup
# of the hard drive
#
# Source Metadata
# - Author: First Name, Last Name
# - Date: December 15, 2022
# - Version: 2.0
# - Copyright: Copyright 2022, XXXX Inc.
# ================================================================================
# ================================================================================
# Set command path lengths
NUM_DIRS=4 # Number of backups allowed on the backup drive
make_dir=/usr/bin/mkdir
remove_dir=/usr/bin/rm
nfind=/usr/bin/find
cdat=/usr/bin/rsync
log_path=~/backup.log
# --------------------------------------------------------------------------------
# Define base directory for backup drive based on Linux distrobution
cur_dir=`pwd`
linux_file=/etc/os-release
# - This if statement will determine the correct media directory where
# the backup drive will be mounted
if grep -q "Arch" $linux_file
then
# The host is an Arch based distribution
media_dir=/run/media/$USERNAME/
elif grep -q "Ubuntu" $linux_file || grep -q "Pop" $linux_file
then
# The host is an Ubuntu or Pop OS based distribution
media_dir=/media/$USERNAME/
else
# The host is not a compatible distribution
echo "Linux distribution not supported, exiting"
exit 1
fi
# --------------------------------------------------------------------------------
# Ensure that rsync is appropriately installed
if ! command -v rsync > /dev/null 2>&1 && grep -q "Arch" $linux_file
then
echo "rsync is not installed"
echo "Install rsync with the command 'sudo pacman -S rsync'"
exit 3
elif ! command -v rsync > /dev/null 2>&1
then
echo "rsync is not installed"
echo "Install rsync with the command 'sudo apt install rsync'"
exit 3
fi
# --------------------------------------------------------------------------------
# Determine which drive to pair with the base directory
# This command will determine the mounted directories in the media directory
potential_dirs=($(ls -l $media_dir | awk '/^d/ {print $9}'))
# Let the user select the correct drive to contain the backup
count=0
echo "Select the number of the appropriate backup directory"
for dir in ${potential_dirs[@]};
do
echo $count")" $dir
let count++
done
echo $count") None"
read option;
# Verify the user entered the correct value
if [ $option -eq $count ];
then
echo "User required option not available, exiting!"
exit 0
fi
if [ $option -gt $count ] || [ $option -lt 0 ];
then
echo "User entered and invalid number, exiting!"
exit 2
fi
# Verify the correct drive was selected
echo "Are you sure ${potential_dirs[option]} is the correct option (Y/N)"
read assertion
if [ "$answer" != "${answer#[Yy]}" ] ;
then
echo "Exiting!"
return 0
fi
DATE=`date +%F:%H:%M:%S`
base_dir=$media_dir${potential_dirs[option]}'/'
backup_drive=$media_dir${potential_dirs[option]}'/'$DATE
# Create directory
sudo $make_dir $backup_drive
# --------------------------------------------------------------------------------
# Backup data
sudo $cdat -aAXHv --delete --exclude={"/dev/*","/proc/*","/sys/*","/run/*","/mnt/*","/media/*","lost+found","swapfile"} / $backup_drive
# --------------------------------------------------------------------------------
# Determine the number of directories in the backup dir and number to be deleted
# Count the number of directories in the backup directory
dir_num=`$nfind $base_dir -mindepth 1 -maxdepth 1 -type d | wc -l`
# Determine the number of directories to be deleted
num_delete="$(($dir_num-$NUM_DIRS))"
# Change to backup directory
cd $base_dir
# Delete the oldest directories if necessary
if [ $num_delete -gt 0 ] ; then
dirs=`ls -d */ | cut -f1 -d'/' | head -n $num_delete`
for variable in $dirs
do
echo "Removing $variable directory"
sudo rm -r $variable
done
fi
# Return to the initial directory
cd `pwd`
# Write succesful results to log file
good_msg=$USERNAME' hard drive succesfully backed up on '$DATE#
/usr/bin/echo $good_msg >> $log_path
# ================================================================================
# ================================================================================
exit 0
답변1
를 사용하고 있기 때문입니다 $USERNAME
. 실제로 작동하는 모습을 쉽게 볼 수 있습니다.
$ bash -c 'echo "USERNAME is $USERNAME"'
USERNAME is terdon
$ sudo bash -c 'echo "USERNAME is $USERNAME"'
[sudo] password for terdon:
USERNAME is
위에서 볼 수 있듯이 $USERNAME
변수는 사용될 때 정의되지 않습니다 sudo
. 이는 다음 줄이 문제의 원인임을 의미합니다.
if grep -q "Arch" $linux_file
then
# The host is an Arch based distribution
media_dir=/run/media/$USERNAME/
elif grep -q "Ubuntu" $linux_file || grep -q "Pop" $linux_file
then
# The host is an Ubuntu or Pop OS based distribution
media_dir=/media/$USERNAME/
else
# The host is not a compatible distribution
echo "Linux distribution not supported, exiting"
exit 1
fi
$USERNAME
아직 설정되지 않았 으므로 /run/media/
Arch와 /media/
다른 두 개만 얻을 수 있습니다. 간단한 해결책은 다음을 사용하는 것입니다 $SUDO_USER
.
$ bash -c 'echo "USERNAME is $SUDO_USER"'
USERNAME is
$ sudo bash -c 'echo "USERNAME is $SUDO_USER"'
USERNAME is terdon
위에서 볼 수 있듯이 $SUDO_USER
해당 명령을 호출한 사용자의 이름이 저장됩니다 sudo
. 따라서 스크립트를 이 방식으로만 실행하려는 경우 이를 사용할 수 있습니다. 여전히 루트로 실행되지 않는 옵션을 허용하려면 테스트를 추가할 수 있습니다.
if [ -n "$SUDO_USER" ]; then
username="$SUDO_USER"
else
username="$USERNAME"
fi
그런 다음 스크립트의 나머지 부분에서 $username
어디에서나 사용했다면 $USERNAME
.
$USER
또는 사용 시에도 설정되는 표준 변수를 사용할 수 있습니다 sudo
.
$ bash -c 'echo "USERNAME is $USER"'
USERNAME is terdon
$ sudo bash -c 'echo "USERNAME is $USER"'
USERNAME is root
root
그러나 이는 다음으로 실행될 때 반환되므로 어쨌든 sudo
해당 방법을 사용하는 것이 더 좋습니다 SUDO_USER
.
마지막으로 스크립트에 대한 몇 가지 참고 사항은 다음과 같습니다.
따옴표가 없는 변수가 많이 있는데 이는 일반적으로 좋지 않은 생각이지만 임의의 경로와 파일 이름을 다루는 경우에는 특히 좋지 않습니다. 바라보다공백이나 기타 특수 문자 때문에 쉘 스크립트가 멈추는 이유는 무엇입니까?그리고bash/POSIX 쉘에서 변수를 인용하는 것을 잊어버리는 보안 위험.
오류 검사를 수행하지 않고 모든 명령이 유효하다고 가정합니다. 이는 명령이 포함된 스크립트를 실행할
sudo rm
때 매우 위험합니다 . 변수가 설정되지 않거나cd
실패하여(예: 설정되지 않거나 유사한 이유로 인해 ) 존재하지 않는 디렉터리$USERNAME
로 이어지는 경우 전체 시스템을 쉽게 중단시킬 수 있습니다.cd
실제로 이런 일이 일어날 수 있습니다. 몇 년 전의 유명한 사례는 다음과 같습니다.Linux용 끔찍한 Steam 버그로 인해 PC의 모든 개인 파일이 삭제됩니다.. 따라서 스크립트 시작 부분
|| exit
에도 다양한 중요한 명령을 추가해야 합니다 .set -x
여러 인스턴스가 구문 분석되고 있습니다
ls
. 이것은역시 아주 나쁜 생각이야그리고 쉽게 부서집니다. 따라서 다음 대신:potential_dirs=($(ls -l $media_dir | awk '/^d/ {print $9}'))
이것을 사용하십시오 :
potential_dirs=( "$media_dir"/*/ )
사용중인 다음 부분에는 다양한 문제가 있습니다
ls
.# Change to backup directory cd $base_dir # Delete the oldest directories if necessary if [ $num_delete -gt 0 ] ; then dirs=`ls -d */ | cut -f1 -d'/' | head -n $num_delete` for variable in $dirs do echo "Removing $variable directory" sudo rm -r $variable done fi
첫째, 가장 오래된 디렉터리를 삭제하는 대신 알파벳순으로 첫 번째 디렉터리를 삭제합니다. 다음으로, 디렉토리 이름에 개행 문자가 포함되어 있으면
ls
및 둘 다 실패하며 , 따옴표가 없으므로 공백이 있어도 실패합니다. 게다가 실제로 오류 검사가 없기 때문에 이전 검사가 어떤 이유로든 실패하면 다른 곳에서 파일을 삭제하게 됩니다!rm
rm
"$variable"
cd $base_dir
따라서 제거하려면 위의 내용을 다음과 같이 변경하십시오.
$num_delete
가장 오래된목차:# Delete the oldest directories if necessary if [ $num_delete -gt 0 ] ; then readarray -d '' dirs < <(stat --printf='%Y %n\0' "$base_dir"/*/ | sort -znk1,1 | head -zn "$num_delete" | cut -z -d ' ' -f2-) for variable in "${dirs[@]}" do echo "Removing $variable directory" sudo rm -r -- "$variable" done fi
이는 반드시 필요한 것은 아닙니다
cd
.이름에 개행 문자가 포함된 경우 디렉터리 계산 방법이 실패합니다.
dir_num=`$nfind $base_dir -mindepth 1 -maxdepth 1 -type d | wc -l`
대신 이것을 사용하면 작동합니다어느이름:
dir_num=$($nfind $base_dir -mindepth 1 -maxdepth 1 -type d -printf '.\n' | wc -l)
역따옴표(
`command`
)는 사용하지 마세요$(command)
. 더 이상 사용되지 않습니다. 존재하는 모든 위치var=`command`
에 로 대체될 수 있습니다var=$(command)
.아니요
cur_dir=`pwd`
. 현재 디렉터리는 이미 특수$PWD
변수에 저장되어 있으므로 간단히$PWD
.스크립트 끝에는 다음과 같은 내용이 있습니다.
cd `pwd`
"명령이 반환하는 디렉터리로 이동"이라고 말하므로 이는 기본적으로 작동하지 않습니다. 따라서 현재 디렉터리만 인쇄되므로
pwd
실제로 아무데도 이동할 수 없습니다 .`pwd`
진심이신 것 같지만cd "$cur_dir"
그것마저도 불필요합니다. 위의 3번 항목에서 볼 수 있듯이cd
처음에는 그럴 필요가 없습니다.