sudo가 새 디렉토리가 생성되는 디렉토리를 변경하는 이유는 무엇입니까?

sudo가 새 디렉토리가 생성되는 디렉토리를 변경하는 이유는 무엇입니까?

나는 이 명령을 사용하여 하드 드라이브를 백업하는 데 필요한 작업을 자동화하도록 Bash에서 스크립트를 작성하고 있습니다 rsync. 이 구현에서는 다음 단계를 완료하기 위한 스크립트를 설정했습니다.

  1. Linux 배포판에 따라 백업 마운트를 위한 기본 디렉터리를 결정합니다.
  2. rsync가 설치되어 있는지 확인하고, 그렇지 않은 경우 사용자에게 설치하라는 메시지를 표시합니다.
  3. 사용자에게 기본 디렉터리에 올바르게 설치된 드라이브를 선택하라는 메시지를 표시합니다.
  4. 올바른 디렉터리에 백업 파일을 생성합니다. 여기서 백업 디렉터리 제목은 다음과 같습니다.YYYY-MM-DD:H:M:S
  5. 백업용으로 사용rsync
  6. 백업 디렉터리가 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:Ssudosudo

전방 참조는 /usr/local/binmy에 있으므로 대체하거나 대체하여 PATH스크립트를 호출 할 수 있습니다.backup./backupbash 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.


마지막으로 스크립트에 대한 몇 가지 참고 사항은 다음과 같습니다.

  1. 따옴표가 없는 변수가 많이 있는데 이는 일반적으로 좋지 않은 생각이지만 임의의 경로와 파일 이름을 다루는 경우에는 특히 좋지 않습니다. 바라보다공백이나 기타 특수 문자 때문에 쉘 스크립트가 멈추는 이유는 무엇입니까?그리고bash/POSIX 쉘에서 변수를 인용하는 것을 잊어버리는 보안 위험.

  2. 오류 검사를 수행하지 않고 모든 명령이 유효하다고 가정합니다. 이는 명령이 포함된 스크립트를 실행할 sudo rm때 매우 위험합니다 . 변수가 설정되지 않거나 cd실패하여(예: 설정되지 않거나 유사한 이유로 인해 ) 존재하지 않는 디렉터리 $USERNAME로 이어지는 경우 전체 시스템을 쉽게 중단시킬 수 있습니다.cd

    실제로 이런 일이 일어날 수 있습니다. 몇 년 전의 유명한 사례는 다음과 같습니다.Linux용 끔찍한 Steam 버그로 인해 PC의 모든 개인 파일이 삭제됩니다.. 따라서 스크립트 시작 부분 || exit에도 다양한 중요한 명령을 추가해야 합니다 .set -x

  3. 여러 인스턴스가 구문 분석되고 있습니다 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및 둘 다 실패하며 , 따옴표가 없으므로 공백이 있어도 실패합니다. 게다가 실제로 오류 검사가 없기 때문에 이전 검사가 어떤 이유로든 실패하면 다른 곳에서 파일을 삭제하게 됩니다!rmrm"$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.

  4. 이름에 개행 문자가 포함된 경우 디렉터리 계산 방법이 실패합니다.

    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)
    
  5. 역따옴표( `command`)는 사용하지 마세요 $(command). 더 이상 사용되지 않습니다. 존재하는 모든 위치 var=`command`에 로 대체될 수 있습니다 var=$(command).

  6. 아니요 cur_dir=`pwd`. 현재 디렉터리는 이미 특수 $PWD변수에 저장되어 있으므로 간단히 $PWD.

  7. 스크립트 끝에는 다음과 같은 내용이 있습니다.

    cd `pwd`
    

    "명령이 반환하는 디렉터리로 이동"이라고 말하므로 이는 기본적으로 작동하지 않습니다. 따라서 현재 디렉터리만 인쇄되므로 pwd실제로 아무데도 이동할 수 없습니다 . `pwd`진심이신 것 같지만 cd "$cur_dir"그것마저도 불필요합니다. 위의 3번 항목에서 볼 수 있듯이 cd처음에는 그럴 필요가 없습니다.

관련 정보