웹사이트 검색

Linux Bash 스크립트에서 eval을 사용하는 방법


모든 Bash 명령 중에서 좋지 않은 오래된 eval이 아마도 최악의 평판을 가지고 있을 것입니다. 정당한가, 아니면 그냥 나쁜 언론? 가장 사랑받지 못하는 Linux 명령의 사용과 위험에 대해 논의합니다.

eval에 대해 이야기해야 합니다.

부주의하게 사용하면 eval은 예측할 수 없는 동작과 심지어 시스템 불안정을 초래할 수 있습니다. 그것의 소리에서 우리는 그것을 사용하지 말아야 할 것입니다, 그렇죠? 글쎄요.

자동차에 대해서도 비슷한 말을 할 수 있습니다. 잘못된 손에 들어가면 치명적인 무기가 됩니다. 사람들은 그들을 램레이드와 도주 차량으로 사용합니다. 우리 모두 자동차 사용을 중단해야 할까요? 아니, 당연하지. 그러나 그것들은 적절하게 사용되어야 하며 그것들을 운전하는 방법을 아는 사람들에 의해 사용되어야 합니다.

eval에 적용되는 일반적인 형용사는 evil입니다. 그러나 그것은 모두 그것이 어떻게 사용되는지에 달려 있습니다. eval 명령은 하나 이상의 변수에서 을 수집합니다. 명령 문자열을 생성합니다. 그런 다음 해당 명령을 실행합니다. 이는 스크립트 실행 중에 명령 내용이 동적으로 파생되는 상황에 대처해야 할 때 유용합니다.

스크립트 외부 어딘가에서 수신한 문자열에 eval을 사용하도록 스크립트를 작성할 때 문제가 발생합니다. 사용자가 입력하거나, API를 통해 전송하거나, HTTPS 요청에 태그를 지정하거나, 스크립트 외부의 다른 곳에서 사용할 수 있습니다.

eval이 작동할 문자열이 로컬에서 프로그래밍 방식으로 파생되지 않은 경우 문자열에 내장된 악성 명령이나 기타 잘못된 형식의 입력이 포함될 위험이 있습니다. 당연히 eval이 악의적인 명령을 실행하는 것을 원하지 않습니다. 따라서 안전을 위해 외부에서 생성된 문자열이나 사용자 입력과 함께 eval을 사용하지 마십시오.

eval을 사용한 첫 단계

eval 명령은 내장 Bash 셸 명령입니다. Bash가 있으면 eval이 나타납니다.

eval은 매개변수를 단일 문자열로 연결합니다. 단일 공백을 사용하여 연결된 요소를 구분합니다. 인수를 평가한 다음 전체 문자열을 실행할 쉘로 전달합니다.

wordcount라는 변수를 만들어 봅시다.

wordcount="wc -w raw-notes.md"

문자열 변수에는 raw-notes.md라는 파일의 단어 수를 세는 명령이 포함되어 있습니다.

eval을 사용하여 변수의 을 전달하여 해당 명령을 실행할 수 있습니다.

명령은 하위 쉘이 아닌 현재 쉘에서 실행됩니다. 우리는 이것을 쉽게 보여줄 수 있습니다. variables.txt라는 짧은 텍스트 파일이 있습니다. 이 두 줄이 포함되어 있습니다.

first=How-To
second=Geek

cat을 사용하여 이 줄을 터미널 창으로 보냅니다. 그런 다음 eval을 사용하여 cat 명령을 평가하여 텍스트 파일 내의 지침이 실행되도록 합니다. 그러면 변수가 설정됩니다.

cat variables.txt
eval "$(cat variables.txt)"
echo $first $second

echo를 사용하여 변수 값을 인쇄하면 eval 명령이 하위 쉘이 아닌 현재 쉘에서 실행되는 것을 볼 수 있습니다.

하위 셸의 프로세스는 상위 셸 환경을 변경할 수 없습니다. eval은 현재 셸에서 실행되기 때문에 eval에 의해 설정된 변수는 eval 명령을 실행한 셸에서 사용할 수 있습니다.

스크립트에서 eval을 사용하는 경우 eval에 의해 변경되는 셸은 스크립트를 시작한 셸이 아니라 스크립트가 실행 중인 하위 셸입니다.

명령 문자열에서 변수 사용

명령 문자열에 다른 변수를 포함할 수 있습니다. 정수를 저장하기 위해 두 개의 변수를 설정합니다.

num1=10 
num2=7

두 숫자의 합을 반환하는 expr 명령을 저장할 변수를 생성합니다. 이는 명령에서 두 정수 변수의 값에 액세스해야 함을 의미합니다. expr 문 주위의 백틱에 유의하십시오.

add="`expr $num1 + $num2`"

expr 문의 결과를 표시하는 다른 명령을 생성합니다.

show="echo"

echo 문자열의 끝이나 expr 문자열의 시작 부분에는 공백을 포함할 필요가 없습니다. eval이 이를 처리합니다.

전체 명령을 실행하기 위해 다음을 사용합니다.

eval $show $add

expr 문자열 내부의 변수 값은 실행할 쉘로 전달되기 전에 eval 에 의해 문자열로 대체됩니다.

변수 내부의 변수에 접근하기

변수에 값을 할당한 다음 해당 변수의 이름을 다른 변수에 할당할 수 있습니다. eval을 사용하면 두 번째 변수에 저장된 이라는 이름에서 첫 번째 변수에 있는 에 액세스할 수 있습니다. 예를 들어 문제를 푸는 데 도움이 될 것입니다.

이 스크립트를 편집기에 복사하고 assign.sh라는 파일로 저장합니다.

#!/bin/bash

title="How-To Geek"
webpage=title
command="echo"
eval $command \${$webpage}

chmod 명령으로 실행 가능하게 만들어야 합니다.

chmod +x assign.sh

이 문서에서 복사하는 모든 스크립트에 대해 이 작업을 수행해야 합니다. 각각의 경우에 적절한 스크립트 이름을 사용하십시오.

스크립트를 실행할 때 eval 명령이 webpage 변수를 사용하고 있어도 title 변수의 텍스트를 볼 수 있습니다.

./assign.sh

이스케이프된 달러 기호 $ 및 중괄호 {}로 인해 eval은 webpage 변수.

동적으로 생성된 변수 사용

eval을 사용하여 동적으로 변수를 생성할 수 있습니다. 이 스크립트를 loop.sh라고 합니다.

#!/bin/bash

total=0
label="Looping complete. Total:"

for n in {1..10}
do
  eval x$n=$n
  echo "Loop" $x$n
  ((total+=$x$n))
done

echo $x1 $x2 $x3 $x4 $x5 $x6 $x7 $x8 $x9 $x10

echo $label $total

우리가 생성한 변수 값의 합계를 보유하는 total이라는 변수를 생성합니다. 그런 다음 label이라는 문자열 변수를 생성합니다. 이것은 간단한 텍스트 문자열입니다.

우리는 10번 반복하고 x1에서 x10까지 10개의 변수를 생성할 것입니다. 루프 본문의 eval 문은 x를 제공하고 루프 카운터 $n의 값을 가져와 변수 이름을 만듭니다. 동시에 새 변수를 루프 카운터 $n의 값으로 설정합니다.

새 변수를 터미널 창에 인쇄한 다음 total 변수를 새 변수 값으로 증가시킵니다.

루프 외부에서 10개의 새 변수가 다시 한 번 모두 한 줄에 인쇄됩니다. 이름의 계산 또는 파생 버전을 사용하지 않고 실제 이름으로도 변수를 참조할 수 있습니다.

마지막으로 total 변수의 값을 인쇄합니다.

./loop.sh

배열과 함께 eval 사용

오래 실행되고 일부 처리를 수행하는 스크립트가 있는 시나리오를 상상해 보십시오. 타임스탬프에서 생성된 이름으로 로그 파일에 씁니다. 경우에 따라 새 로그 파일을 시작합니다. 스크립트가 완료되면 오류가 없으면 생성한 로그 파일을 삭제합니다.

단순히 rm *.log하는 것이 아니라 생성한 로그 파일만 삭제하는 것이 좋습니다. 이 스크립트는 해당 기능을 시뮬레이트합니다. 이것은 clear-logs.sh입니다.

#!/bin/bash

declare -a logfiles

filecount=0 
rm_string="echo"

function create_logfile() {
  ((++filecount))
  filename=$(date +"%Y-%m-%d_%H-%M-%S").log
  logfiles[$filecount]=$filename
  echo $filecount "Created" ${logfiles[$filecount]}
}

# body of the script. Some processing is done here that
# periodically generates a log file. We'll simulate that
create_logfile
sleep 3
create_logfile
sleep 3
create_logfile
sleep 3
create_logfile

# are there any files to remove?
for ((file=1; file<=$filecount; file++))
do
  # remove the logfile
  eval $rm_string ${logfiles[$file]} "deleted..."
  logfiles[$file]=""
done

스크립트는 logfiles 라는 배열을 선언합니다. 이것은 스크립트에 의해 생성된 로그 파일의 이름을 보유합니다. filecount 라는 변수를 선언합니다. 이것은 생성된 로그 파일의 수를 보유합니다.

또한 rm_string이라는 문자열을 선언합니다. 실제 스크립트에서는 rm 명령이 포함되지만 echo를 사용하여 비파괴적인 방식으로 원리를 설명할 수 있습니다.

create_logfile() 함수는 각 로그 파일의 이름이 지정되고 열리는 위치입니다. 파일 이름만 생성하고 파일 시스템에서 생성된 것처럼 가장합니다.

함수는 filecount 변수를 증가시킵니다. 초기 값은 0이므로 생성한 첫 번째 파일 이름은 배열의 위치 1에 저장됩니다. 이것은 의도적으로 수행되며 나중에 참조하십시오.

파일 이름은 date 명령과 .log 확장자를 사용하여 생성됩니다. 이름은 filecount로 표시된 위치의 배열에 저장됩니다. 이름이 터미널 창에 인쇄됩니다. 실제 스크립트에서는 실제 파일도 생성합니다.

스크립트 본문은 sleep 명령을 사용하여 시뮬레이트됩니다. 첫 번째 로그 파일을 만들고 3초 동안 기다린 다음 다른 파일을 만듭니다. 파일 이름의 타임스탬프가 서로 다르도록 간격을 두고 4개의 로그 파일을 만듭니다.

마지막으로 로그 파일을 삭제하는 루프가 있습니다. 루프 카운터 파일은 1로 설정됩니다. 생성된 파일 수를 포함하는 filecount 값까지 계산됩니다.

로그 파일이 생성되지 않았기 때문에 filecount가 여전히 0으로 설정되어 있으면 1이 0보다 작거나 같지 않기 때문에 루프 본문이 실행되지 않습니다. 이것이 filecount 변수가 선언될 때 0으로 설정되고 첫 번째 파일이 생성되기 전에 증가된 이유입니다.

루프 내에서 eval을 비파괴 rm_string 및 배열에서 검색된 파일 이름과 함께 사용합니다. 그런 다음 배열 요소를 빈 문자열로 설정합니다.

이것은 우리가 스크립트를 실행할 때 우리가 보는 것입니다.

./clear-logs.sh

모두 나쁘지는 않습니다

많은 비방을 받는 eval은 확실히 용도가 있습니다. 대부분의 도구와 마찬가지로 무분별하게 사용하면 여러 가지 면에서 위험합니다.

작동하는 문자열이 내부적으로 생성되고 사람, API 또는 HTTPS 요청과 같은 것에서 캡처되지 않는지 확인하면 주요 함정을 피할 수 있습니다.