DevOps/Shell

Shell Script - 10, Function

게임이 더 좋아 2022. 11. 25. 17:29
반응형
728x170

스크립트에서 사용자 정의 함수를 만들 수 없다면

스크립트라고 할 수도 없다.

스크립트의 꽃 함수를 알아보자

 

일반적으로 함수는 2가지 형태로 쓰인다.

1).h 헤더파일(라이브러리)과 같이 다른 파일에 선언, 정의되고 include 만 해서 쓰이는 경우(linking 작업이 일어남)

2)같은 파일에 선언,정의가 되어 자체적으로 바로 쓰는 경우

 

둘 다 상관없다.

하지만 library를 쓰는 경우는 예외가 있다.

아래 command가 예외다.

항상 스크립트 시작에 써놓아야 한다.

. ./library.sh

 

헷갈리는 것들이 몇가지 있는데

일반적으로는 function과 procedure는 다르다고 생각한다.

 function은 값을 반환하고 procedure는 출력한다.

 

쉘 에서는 둘 중 하나만, 둘 다, 아무것도 안 할 수도 있다.

쉘 스크립트에서 function은 조금 다른 기대를 하게 된다.

 

쉘 스크립트에서는 값을 반환할 수도 있는 동작이 4가지나 있다.

  1. 변수나 변수들의 상태를 바꾸는 것
  2. exit 명령을 사용하여 쉘 스크립트를 나오는 것
  3. return 명령을 사용하여 현재 함수를 끝내고 다른 쉘 스크립트 구역에서 호출한 부분에 값을 반환하는 것
  4. c=`expr $a + $b` 처럼 stdout을 output을 호출자에게 넘기는 것



보면 C랑 비슷한 메커니즘을 가진다.

exit이 program을 나오고

return이 control을 caller에게 준다.

쉘 스크립트와의 차이는 쉘에선 parameters를 바꿀 수 없다는 것이다.

바꾸기 위해서는 global로 선언하는 것이 필수다.



function을 쓰는 스크립트를 보자

 

function.sh

 
#!/bin/sh
# A simple script with a function...

add_a_user()
{
  USER=$1
  PASSWORD=$2
  shift; shift;
  # Having shifted twice, the rest is now comments ...
  COMMENTS=$@
  echo "Adding user $USER ..."
  echo useradd -c "$COMMENTS" $USER
  echo passwd $USER $PASSWORD
  echo "Added user $USER ($COMMENTS) with pass $PASSWORD"
}

###
# Main body of script starts here
###
echo "Start of script..."
add_a_user bob letmein Bob Holness the presenter
add_a_user fred badpassword Fred Durst the singer
add_a_user bilko worsepassword Sgt. Bilko the role model
echo "End of script..."



add_a_user( )를 보면 함수를 선언하는 것을 알 수 있다.

그 뒤에는 {, bracket이 따라온다.

실제 function의 로직을 작성하는 부분이다.

 

이 함수는 call 할 때까지 실행되지 않는다.

다시 말해서 read는 해도 실행하지는 않는다는 말이다.

 

함수 중간을 보면 echo 뒤에 useradd 와 passwd라는 커맨드가 있다.

이것은 debugging 기술인데 실제로 올바른 커맨드가 입력되었는지 체크해준다.

다시 말해서, 루트가 아니어도 스크립트가 실행되고, 닷지 계정을 추가하지 않아도 작동한다는 뜻이다.

 

이러한 기술을 function에만 쓰는 것은 아니다.

 

우선 여기서 function에서 쓰인 parameter들은

아래와 같다.

 
$1=bob
$2=letmein
$3=Bob
$4=Holness
$5=the
$6=presenter



C언어를 보면 역시나 파라미터들을 조작할 수 있다.

쉘 스크립트에서도 조작할 수 있다.

$1을 조작하고 싶다면 

 

A=$1

같이 function을 호출하기 전에 할당해서 사용할 수 있다.

그렇게 함수 안에서는 $A를 선언하여 값을 참조할 수 있다.

 

그렇다면 함수 안에서의 변수 범위는 어떻게 될까??

 

우선 쉘 스크립트에서는 scope가 없다. 

parameter 들에만  scope가 있을 뿐이다.

 
#!/bin/sh

myfunc()
{
  echo "I was called as : $@"
  x=2
}

### Main script starts here

echo "Script was called with $@"
x=1
echo "x is $x"
myfunc 1 2 3
echo "x is $x"

 

위 스크립트를 실행해보자

Script was called with a b c
x is 1
I was called as : 1 2 3
x is 2


 

x를 call 하기 전에 선언했기 때문에

함수 안에서 x를 조작하는 것이 유효했다.

 

또한 $@ 는 함수가 어떻게 호출되느냐에 반영되어 나온다.

x 가 global로 선언되었고 myfunc( )에서 change를 했다.

여전히 main 으로 돌아와도 값을 유지하고 있다.

 

그렇다면 만약 sub-shell 에서 호출되면 어떻게 될까? 

 
myfunc 1 2 3 | tee out.log

위처럼 다른 곳으로 pipe 했다면 x는 1이라고 나올 것이다.

x를 조작한 것은 다른쉘에서 작업했기 때문이다.

 

이렇게 다른 쉘에서 부르게 되면 디버깅하기가 어려워진다.



그리고 함수는 value를 바꿀 수 없다.

다시 말하면 파라미터로 전달되는 변수가 아니라

변수 자체적으로 바꿔야 한다는 말이다.

 

예시를 들어보자

#!/bin/sh

myfunc()
{
  echo "\$1 is $1"
  echo "\$2 is $2"
  # cannot change $1 - we'd have to say:
  # 1="Goodbye Cruel"
  # which is not a valid syntax. However, we can
  # change $a:
  a="Goodbye Cruel"
}

### Main script starts here

a=Hello
b=World
myfunc $a $b
echo "a is $a"
echo "b is $b"


 

실행결과는??

 



a 값이 바뀐 것을 알 수 있다.

역시 밖에서 선언해서 값이 바뀐 것을 알 수 있다.




과연 Recursion 같은 재귀함수도 만들 수 있을까??

그렇다.

예를 들어보자

 

factorial.sh

 
#!/bin/sh
factorial()
{
  if [ "$1" -gt "1" ]; then
    i=`expr $1 - 1`
    j=`factorial $i`
    k=`expr $1 \* $j`
    echo $k
  else
    echo 1
  fi
}

while :
do
  echo "Enter a number:"
  read x
  factorial $x
done



x에 대한 값을 parameter로 넣고

값을 function에서 돌린다.

1이 될 때까지 자기 자신을 부른다.




여기까지는 function을 같은 스크립트 안에서 선언했는데

만약 밖에서 선언,정의를 하면 어떨까??

 

common.lib

 
# common.lib
# Note no #!/bin/sh as this should not spawn
# an extra shell. It's not the end of the world
# to have one, but clearer not to.
#
STD_MSG="About to rename some files..."

rename()
{
  # expects to be called as: rename .txt .bak
  FROM=$1
  TO=$2

  for i in *$FROM
  do
    j=`basename $i $FROM`
    mv $i ${j}$TO
  done
}



function2.sh

 
#!/bin/sh
# function2.sh
. ./common.lib
echo $STD_MSG
rename .txt .bak



function3.sh

#!/bin/sh
# function3.sh
. ./common.lib
echo $STD_MSG
rename .html .html-bak




 

function2와 3의 공통점은 같은 쉘에서 library를 부른다.

그럼으로써 STD_MSG도 사용할 수 있게 된다.

 

**명심해야될 것은 현재 쉘에서 실행할 것!!! 관련된 변수를 쓰거나 조작하고 싶다면 그렇게 해야 한다.

 

함수에서 Return할 때 몇가지 알아야 할 것들이 있다.

알아보자

 
#!/bin/sh

adduser()
{
  USER=$1
  PASSWORD=$2
  shift ; shift
  COMMENTS=$@
  useradd -c "${COMMENTS}" $USER
  if [ "$?" -ne "0" ]; then
    echo "Useradd failed"
    return 1
  fi
  passwd $USER $PASSWORD
  if [ "$?" -ne "0" ]; then
    echo "Setting password failed"
    return 2
  fi
  echo "Added user $USER ($COMMENTS) with pass $PASSWORD"
}

## Main script starts here

adduser bob letmein Bob Holness from Blockbusters
ADDUSER_RETURN_CODE=$?
if [ "$ADDUSER_RETURN_CODE" -eq "1" ]; then
  echo "Something went wrong with useradd"
elif [ "$ADDUSER_RETURN_CODE" -eq "2" ]; then
  echo "Something went wrong with passwd"
else
  echo "Bob Holness added to the system."
fi

중간에 return 커맨드가 있고

return 하는 값이 1, 2 가 있다.

 

저 스크립트에서는 useradd 와 passwd를 체크한다.

체크하고 결과에 따라 1 은 부적절한 useradd 

2는 부적절한 passwd를 뜻한다.

그렇게 함으로써 calling script (호출자)가 정상적인 수행이 되었는지 return 값으로 알 수 있다.

 

반응형
그리드형

'DevOps > Shell' 카테고리의 다른 글

environment variable  (0) 2022.12.31
Bash, 쉘 스크립트 시작하기  (0) 2022.12.25
Shell Script - 9, External Program  (0) 2022.11.25
Shell Script - 8, Variable(2)  (0) 2022.11.24
Shell Script - 7, Case  (0) 2022.11.24