The linux command line: 32 - positional parameters
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
에서도 사용할 수 있다.
PROGNAME
이 FUNCNAME
변수로 바뀐 것을 주목. 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