backend/ubuntu

The linux command line: 32 - positional parameters

seul chan 2021. 6. 27. 16:53

32. Positional parameters

shell 프로그램이 command line에서 컨텐츠를 받을 수 있는 법을 다룸.

Accessing the command line

shell은 positional parameter라고 불리는 변수를 제공함.

이 변수는 0~9까지 이름이 붙어있고, 다음 방식으로 사용됨.

#!/bin/bash

# posit-param: script to view command line parameters

echo "
\$0 = $0
\$1 = $1
\$2 = $2
\$3 = $3
\$4 = $4
\$5 = $5
\$6 = $6
\$7 = $7
\$8 = $8
\$9 = $9
"

이를 파라미터 없이 실행시키면 다음과 같음.

[me@linuxbox ~]$ posit-param

$0 = /home/me/bin/posit-param
$1 =
$2 =
$3 =
$4 =
$5 =
$6 =
$7 =
$8 =
$9 =

아무 argument가 없음에도 0 변수로 실행된 pathname이 나타난 것을 볼 수 있다.

argument를 넣어서 실행시키면 1 변수부터 순차적으로 들어간다.

[me@linuxbox ~]$ posit-param a b c d

$0 = /home/me/bin/posit-param
$1 = a
$2 = b
$3 = c
$4 = d
$5 =
$6 =
$7 =
$8 =
$9 =

paarameter expansion을 사용하여 9 이상의 변수를 사용할 수 있음. 9보다 큰 숫자는 중괄호로 묶어줘야한다. ${10}, ${55} 등...

Determining the Number of arguments

쉘은 $#라는 변수를 제공하는데, argument의 개수를 의미한다.

#!/bin/bash

# posit-param: script to view command line parameters

echo "
Number of arguments: $#
\$0 = $0
\$1 = $1
\$2 = $2
\$3 = $3
\$4 = $4
\$5 = $5
\$6 = $6
\$7 = $7
\$8 = $8
\$9 = $9
"

이를 실행시키면 다음과 같다.

[me@linuxbox ~]$ posit-param a b c d

Number of arguments: 4
$0 = /home/me/bin/posit-param
$1 = a
$2 = b
$3 = c
$4 = d
$5 =
$6 =
$7 =
$8 =
$9 =

shift - Getting access to many arguments

만약에 프로그램이 아주 많은 argument를 받아야 하면?

[me@linuxbox ~]$ posit-param *

Number of arguments: 82
$0 = /home/me/bin/posit-param
$1 = addresses.ldif
$2 = bin
$3 = bookmarks.html
$4 = debian-500-i386-netinst.iso
$5 = debian-500-i386-netinst.jigdo
$6 = debian-500-i386-netinst.template
$7 = debian-cd_info.tar.gz
$8 = Desktop
$9 = dirlist-bin.txt

위 예시에서 wildcard는 82 argument로 사용된다.

shell은 shift 명령어를 사용해서 파라미터를 실행시마다 "move down one" 할 수 있다.

실제로 shift를 사용하면, (변하지 않는 $0 파라미터를 제외하고) 하나의 파라미터를 사용하는 것이다.

#!/bin/bash

# posit-param2: script to display all arguments

count=1

while [[ $# -gt 0 ]]; do
      echo "Argument $count = $1"
      count=$((count + 1))
      shift
done

shift 명령어가 실행될 때 마다, $2의 값은 $1로, $3의 값은 $2로 변경된다. $1의 값은 사라지고 $#의 값도 1씩 줄어든다.

위 프로그램에서는 argument의 개수 ($#)가 0보다 크면 계속 실행되는 loop를 만들고, shift를 사용하여 계속 $1를 프린트 해주고 있다.

[me@linuxbox ~]$ posit-param2 a b c d
Argument 1 = a
Argument 2 = b
Argument 3 = c
Argument 4 = d

Simple application

shift 명령어 없이도 positional parameter를 사용한 유용한 애플리케이션을 만들 수 있다.

#!/bin/bash

# file-info: simple file information program

PROGNAME="$(basename "$0")"

if [[ -e "$1" ]]; then
      echo -e "\nFile Type:"
      file "$1"
      echo -e "\nFile Status:"
      stat "$1"
else
      echo "$PROGNAME: usage: $PROGNAME file" >&2
      exit 1
fi

위 프로그램은 (file 명령어로 결정되는) file type과 stat 명령어로 나오는 file status르 보여준다.

흥미로운 점 중 하나는 basename "$0"의 결과인 PROGNAME 변수인데, basename 명령어는 pathname의 경로를 모두 제거하고 파일의 이름만 남겨준다.

Using positional parameters with shell functions

shell script에서 사용된 것처럼 argument를 shell function에도 사용할 수 있다.

위에서 만든 file-info를 함수로 만들어보자.

file_info () {

      # file_info: function to display file information

      if [[ -e "$1" ]]; then
            echo -e "\nFile Type:"
            file "$1"
            echo -e "\nFile Status:"
            stat "$1"
      else
            echo "$FUNCNAME: usage: $FUNCNAME file" >&2
            return 1
      fi
}

이런 기능으로, 많은 shell function을 단순히 shell script 뿐 아니라 .bashrc에서도 사용할 수 있다.

PROGNAMEFUNCNAME 변수로 바뀐 것을 주목. shell은 자동으로 해당 변수가 실행시키는 shell function의 이름을 지칭하게 해준다.

Handling positional parameters en Masse

가끔은 positional parameters를 그룹으로 다루는 것이 유용할 수 있다.

이를 위해 shell은 두가지 특수 변수를 제공한다. $*$@

  • $*: 1부터 시작하는 positional parameters 목록으로 확장. 큰따옴표로 묶이면 모든 positional parameter를 포함하는 문자열로 확장. 각각은 공백 문자(IFS 쉘 변수의 첫번째 문자)로 구분.
  • $@: 1부터 시작하는 positional parameters 목록으로 확장. 큰따옴표로 묶이면 각 positional parameter를 큰 따옴표로 묶은 것처럼 별도 단어로 확장.
#!/bin/bash

# posit-params3: script to demonstrate $* and $@

print_params () {
      echo "\$1 = $1"
      echo "\$2 = $2"
      echo "\$3 = $3"
      echo "\$4 = $4"
}

pass_params () {
      echo -e "\n" '$* :';   print_params $*
      echo -e "\n" '"$*" :'; print_params "$*"
      echo -e "\n" '$@ :';   print_params $@
      echo -e "\n" '"$@" :'; print_params "$@"
}

pass_params "word" "words with spaces"

이를 실행시켜보면 각각 다른 결과가 나타난 것을 볼 수 있다.

[me@linuxbox ~]$ posit-param3

 $* :
$1 = word
$2 = words
$3 = with
$4 = spaces

 "$*" :
$1 = word words with spaces
$2 =
$3 =
$4 =

 $@ :
$1 = word
$2 = words
$3 = with
$4 = spaces

 "$@" :
$1 = word
$2 = words with spaces
$3 =
$4 =

A more complete application

이전에 만들던 sys_info_page 프로그램을 다시 작업해보자.

  • Output file: optional로 특정 파일 이름을 받아 output을 저장해보자. -f file or --file file
  • Interactive mode: -i or --interactive
  • Help: -h or --help

우선 shift를 사용해서 받은 parameter들을 각각의 옵션과 매칭시키는 함수를 만들자.

usage () {
      echo "$PROGNAME: usage: $PROGNAME [-f file | -i]"
      return
}

# process command line options

interactive=
filename=

while [[ -n "$1" ]]; do
      case "$1" in
            -f | --file)            shift
                                    filename="$1"
                                    ;;
            -i | --interactive)     interactive=1
                                    ;;
            -h | --help)            usage
                                    exit
                                    ;;
            *)                      usage >&2
                                    exit 1
                                    ;;
      esac
      shift
done

shift를 사용해서 positional parameters를 하나씩 순회하면서 각각 옵션에 따라 실행시킴.

-h일 경우 usage 함수를, -f, -i일 경우 filename, interactive 변수에 값을 넣어준다.

다음으로 interactive mode를 작성해보자.

# interactive mode

if [[ -n "$interactive" ]]; then
      while true; do
            read -p "Enter name of output file: " filename
            if [[ -e "$filename" ]]; then
                  read -p "'$filename' exists. Overwrite? [y/n/q] > "
                  case "$REPLY" in
                      Y|y)    break
                              ;;
                      Q|q)    echo "Program terminated."
                              exit
                              ;;
                      *)      continue
                              ;;
                  esac
            elif [[ -z "$filename" ]]; then
                  continue
            else
                  break
            fi
      done
fi

만약 interactive 변수가 있다면, while loop가 실행되면서 상황에 따라 파일을 생성할지, 덮어쓸지 처리한다.

filename이 있을 때 해당 파일로 output을 기록하는 부분을 구현해보자.

write_html_page () {
      cat <<- _EOF_
      <html>
            <head>
                  <title>$TITLE</title>
            </head>
            <body>
                  <h1>$TITLE</h1>
                  <p>$TIMESTAMP</p>
                  $(report_uptime)
                  $(report_disk_space)
                  $(report_home_space)
            </body>
      </html>
      _EOF_
      return
}

# output html page

if [[ -n "$filename" ]]; then
      if touch "$filename" && [[ -f "$filename" ]]; then
            write_html_page > "$filename"
      else
            echo "$PROGNAME: Cannot write file '$filename'" >&2
            exit 1
      fi
else
      write_html_page
fi

이제 위 기능들을 추가하여 기존보다 훨씬 다양한 기능을 갖춘 프로그램을 작성할 수 있다.

#!/bin/bash

# sys_info_page: program to output a system information page

PROGNAME="$(basename "$0")"
TITLE="System Information Report For $HOSTNAME"
CURRENT_TIME="$(date +"%x %r %Z")"
TIMESTAMP="Generated $CURRENT_TIME, by $USER"

report_uptime () {
      cat <<- _EOF_
            <h2>System Uptime</h2>
            <pre>$(uptime)</pre>
            _EOF_
      return
}

report_disk_space () {
      cat <<- _EOF_
            <h2>Disk Space Utilization</h2>
            <pre>$(df -h)</PRE>
            _EOF_
      return
}

report_home_space () {
      if [[ "$(id -u)" -eq 0 ]]; then
            cat <<- _EOF_
                  <h2>Home Space Utilization (All Users)</h2>
                  <pre>$(du -sh /home/*)</pre>
                  _EOF_
      else
            cat <<- _EOF_
                  <h2>Home Space Utilization ($USER)</h2>
                  <pre>$(du -sh "$HOME")</pre>
                  _EOF_
      fi
      return
}

usage () {
      echo "$PROGNAME: usage: $PROGNAME [-f file | -i]"
      return
}

write_html_page () {
      cat <<- _EOF_
      <html>
            <head>
                  <title>$TITLE</title>
            </head>
            <body>
                  <h1>$TITLE</h1>
                  <p>$TIMESTAMP</p>
                  $(report_uptime)
                  $(report_disk_space)
                  $(report_home_space)
            </body>
      </html>
      _EOF_
      return
}

# process command line options

interactive=
filename=

while [[ -n "$1" ]]; do
    case "$1" in
        -f | --file)        shift
                            filename="$1"
                            ;;
        -i | --interactive) interactive=1
                            ;;
        -h | --help)        usage
                            exit
                            ;;
        *)                  usage >&2
                            exit 1
                            ;;
    esac
    shift
done

# interactive mode

if [[ -n "$interactive" ]]; then
    while true; do
        read -p "Enter name of output file: " filename
        if [[ -e "$filename" ]]; then
            read -p "'$filename' exists. Overwrite? [y/n/q] > "
            case "$REPLY" in
                Y|y)    break
                        ;;
                Q|q)    echo "Program terminated."
                        exit
                        ;;
                *)      continue
                        ;;
            esac
        elif [[ -z "$filename" ]]; then
            continue
        else
            break  
        fi
    done
fi

# output html page

if [[ -n "$filename" ]]; then
      if touch "$filename" && [[ -f "$filename" ]]; then
            write_html_page > "$filename"
      else
            echo "$PROGNAME: Cannot write file '$filename'" >&2
            exit 1
      fi
else
      write_html_page
fi