Positional Parameters
- 스크립트 파일을 실행할 때
- function 을 호출할 때
- 스크립트 파일을 source 할 때
인수를 주게되면 해당 스크립트 및 함수 내에서 positional paramters 가 자동으로 설정됩니다.
첫번째 인수는 $1
, 두번째는 $2
... 식으로 할당되며 scope 은 local 변수와 같습니다.
숫자가 두자리 이상일 경우는 { }
를 사용해야 합니다.
sh
과 같이 array 를 사용할 수 없는 환경에서 활용할 수 있습니다.
$ set -- aaa bbb ccc ddd eee fff ggg hhh iii jjj kkk lll mmm nnn
$ echo $7
ggg
$ echo foo$7bar 123$7456
foogggbar 123ggg456
$ echo $11 # 두자리 이상 숫자
aaa1 # $1 가 aaa 로 확장되고 뒤에 1 이 붙는다
$ echo foo$11bar 123$11456
fooaaa1bar 123aaa1456
$ echo foo${11}bar 123${11}456 # 따라서 두자리 이상 숫자는 { } 를 사용해야 한다.
fookkkbar 123kkk456
positional parameters 에는 직접 값을 대입할 수 없으므로 보통 변수에 먼저 대입한 후 사용합니다.
# positional parameter 는 직접 값을 대입할 수 없다.
$ 2=bbb
2=bbb: command not found
# 따라서 먼저 변수에 대입하여 사용합니다.
login()
{
local container=$1
local user=$2
if test -z "$container"; then
container=ubuntu
fi
if test -z "$user"; then
user=ubuntu
fi
lxc exec "$container" -- sudo --login --user "$user"
}
positional parameters 는 local 변수이므로 함수 내에서 set 명령을 이용해 설정한 값은 해당 함수에서만 사용할 수 있습니다.
#!/bin/bash
fun () {
set -- 11 22 33
echo $1 $2 $3
}
fun
echo $1 $2 $3
##### 실행 결과 #####
./test.sh aa bb cc
11 22 33
aa bb cc # 함수 밖에서는 'set --' 설정이 적용되지 않는다.
$0
스크립트 파일 이름를 나타냅니다.
$ cat test.sh
#!/bin/sh
echo \$0 : $0
echo \$1 : $1
echo \$2 : $2
.................
$ ./test.sh 11 22
$0 : ./test.sh
$1 : 11
$2 : 22
shell 함수는 $0
값이 /bin/bash 가 됩니다.
$ func() { echo \$0 : $0; echo \$1 : $1; echo \$2 : $2; }
$ func 11 22
$0 : /bin/bash
$1 : 11
$2 : 22
sh -c
형식으로 실행했을 경우는 첫번째 인수를 가리키게 됩니다.
$ sh -c 'echo $0 $1 $2' 11 22 33
11 22 33
$ sh -c 'echo $0, $1, $2' # 인수를 주지 않을 경우
sh, ,
$ sh -c 'echo "$@"' 11 22 33 # $@, $* 는 $1 부터 출력하므로
22 33
# 스크립트 파일 실행시 전달한 인수를 그대로 다시 'sh -c' 로 전달하려면
$ cat test.sh
#!/bin/bash
bash -c 'echo "$@"' x "$@" # $0 값은 "x" 가 된다.
$ ./test.sh 11 22 33
11 22 33
Symbolic link 와 $0
명령을 다른 이름으로 symbolic link 또는 hard link 한 후에 링크 이름으로 실행을 하면
$0
값이 링크 이름 됩니다.
$ cat ccache.sh
#!/bin/sh
echo "Im running '$0'"
---------------------------
$ chmod +x ccache.sh
$ ./ccache.sh
Im running './ccache.sh'
$ ln -s ccache.sh gcc.sh
$ ./gcc.sh
Im running './gcc.sh'
$ ln -s ccache.sh clang.sh
$ ./clang.sh
Im running './clang.sh'
$1, $2, $3 ...
각 인수들을 나타냅니다.
--------- test.sh --------
#!/bin/sh
echo number of arguments = $#
echo \$0 = "$0"
echo \$1 = "$1"
echo \$2 = "$2"
echo \$3 = "$3"
######### output ########
$ ./test.sh 11 22 33
number of arguments = 3
$0 = ./test.sh
$1 = 11
$2 = 22
$3 = 33
$#
$0
을 제외한 전체 인수의 개수를 나타냅니다.
$ cat test.sh
#!/bin/sh
echo $#
.....................
$ ./test.sh 11 22 33
3
인수 값이 null 이면 "$@"
는 인수로 잡히지 않습니다.
$ set -- # 현재 설정되어 있는 인수들을 모두 삭제
$ ./test.sh "$@"
0
$ ./test.sh "$*" # "" 이 인수로 잡힌다.
1
$ set 11 22
$ ./test.sh 11 "${@:3}"
1
$ ./test.sh 11 "${*:3}" # "" 이 인수로 잡힌다.
2
$@ , $*
$@
, $*
는 positional parameters 전부를 포함합니다.
array 에서 사용되는 @
, *
기호와 의미가 같습니다.
변수를 quote 하지 않으면 단어분리에 의해 두 변수의 차이가 없지만 quote 을 하게 되면
"$@"
의 의미는 "$1"
"$2"
"$3"
... 와 같게되고
"$*"
의 의미는 "$1x$2x$3 ... "
와 같게 됩니다. ( 여기서 x
는 IFS
변수값의 첫번째 문자 입니다. )
--------- test.sh --------
#!/bin/sh
echo \$@ : $@
echo \$* : $*
echo '======== "$@" ======='
for v in "$@"; do # 또는 for v do ...
echo "$v"
done
echo '======== "$*" ======='
for v in "$*"; do
echo "$v"
done
########### output ##########
$ ./test.sh 11 22 33
$@ : 11 22 33
$* : 11 22 33
======== "$@" =======
11
22
33
======== "$*" =======
11 22 33
"$*"
는 항상 IFS 변수값의 첫번째 문자를 인수 구분자로 갖습니다.
$ set -- 11 22 33 44 55
$ IFS=XYZ
$ echo "$*" , $*
11X22X33X44X55 , 11 22 33 44 55
$ echo "$@" , $@
11 22 33 44 55 , 11 22 33 44 55
$ foo=$* # $* 값을 foo 변수에 대입
$ echo "$foo" , $foo
11X22X33X44X55 , 11 22 33 44 55 # "$foo" 변수값도 "$*" 와 같이 출력된다.
$ bar=$@
$ echo "$bar" , $bar
11 22 33 44 55 , 11 22 33 44 55 (bash)
11X22X33X44X55 , 11 22 33 44 55 (sh)
$ IFS=YZ # IFS 변수값을 YZ 로 변경
$ echo "$*" , $*
11Y22Y33Y44Y55 , 11 22 33 44 55
$ echo "$foo" , $foo # IFS 값이 변경되면 기존의 $foo 변수값은
11X22X33X44X55 , 11X22X33X44X55 # 인수 구분자가 X 로 고정된다.
전달받은 인수들을 그대로 다른 명령의 인수로 다시 전달하려고 할때는 "$@"
를 사용합니다.
외부 명령인 egrep
, fgrep
은 shell 스크립트 파일로 활용방법을 볼 수 있습니다.
$ cat /bin/egrep
#!/bin/sh
exec grep -E "$@" # 전달받은 인수들을 -E 옵션을 추가해서 다시 grep 명령에 전달한다.
$ cat /bin/fgrep
#!/bin/sh
exec grep -F "$@"
---------------------
$ run() { "$@" ;}
$ run date +%D
01/22/16 # "date" 은 명령 "+%D" 는 인수가 된다.
$ run() { "$*" ;}
$ run date +%D
date +%D: command not found # "date +%D" 가 명령 이름이 된다.
set
set 명령은 보통 shell 옵션을 설정할 때 사용하지만 positional paramters 를 설정하거나 삭제할 때도 사용됩니다.
$ set 11 22 33
$ set -- 11 22 33 # 설정하려는 인수값에 '-' 문자가 포함될 경우 ( 예: -b -c -d )
$ echo $1 $2 $3
11 22 33
$ set -- # 현재 설정되어 있는 positional parameters 가 모두 삭제됩니다.
$ echo $1 $2 $3
$
사용예 )
--------- test.sh --------
#!/bin/sh
set -- 11 22 33 # set 명령을 이용하여 script 내에서 positional parameters 를 설정
echo number of arguments = $#
echo \$0 = "$0"
echo \$1 = "$1"
echo \$2 = "$2"
echo \$3 = "$3"
######### output ########
$ ./test.sh
number of arguments = 3
$0 = ./test.sh
$1 = 11
$2 = 22
$3 = 33
--------- set 활용 --------
$ date
Sat Dec 31 07:45:56 KST 2016
$ set -- $(date)
$ echo 오늘은 $2 월 $3 일 $1 요일 $4 시 입니다.
오늘은 Dec 월 31 일 Sat 요일 07:46:29 시 입니다.
shift
shift [n]
shift 명령은 현재 설정되어 있는 positional parameters 를 좌측으로 n
만큼 이동시킵니다.
결과로 n
개의 positional parameters 가 삭제됩니다.
현재 positional parameters 개수 보다 n
값이 크면 삭제가 일어나지 않고
종료 상태 값은 1
이 됩니다.
n
값을 주지 않으면 디폴트는 1 입니다.
$ set -- 11 22 33 44 55
$ echo $@
11 22 33 44 55
$ shift 2
$ echo $@
33 44 55
$ shift 2
$ echo $@
55
$ shift 2 # positional parameters 개수가 1 개만 남은 상태에서 shift 2 를
$ echo $@ # 하였으므로 삭제가 되지 않고 그대로 남아있게 됩니다.
55
$ shift # shift 는 shift 1 와 같으므로 마지막 남은 positional parameters
$ echo $@ # 1 개가 삭제되어 $@ 값은 empty 가 됩니다.
$
shift 한 후에 $1
는 다음 값이 됩니다.
모든 인수가 shift 되면 $1
값은 empty 가 됩니다.
$ set -- 11 22 33
$ echo $1
11
$ shift
$ echo $1
22
$ shift
$ echo $1
33
$ shift
$ echo $1
$ <---- 모든 인수가 shift 되면 다음 값은 empty 가 된다.
for 문에서 순회시 사용되는 $@
값은 shift, set 에의해 변경되는 $@
값과 별도로 존재하므로
shift 는 for 반복문에 영향을 주지 않습니다.
fun1 () { fun2 () {
for arg in "$@"; do for arg in "$@"; do
echo forloop : $arg echo forloop : $arg
echo \$1 : $1 shift 2 && echo \$1 : $1
shift 3 done
done }
}
..............................................................................
# 44 에서 shift 성공 # 44 에서 shift 실패
$ fun1 11 22 33 44 55 66 $ fun1 11 22 33 44 55 $ fun2 11 22 33 44 55
forloop : 11 forloop : 11 forloop : 11
$1 : 11 $1 : 11 $1 : 33
forloop : 22 forloop : 22 forloop : 22
$1 : 44 $1 : 44 $1 : 55
forloop : 33 forloop : 33 forloop : 33
$1 : $1 : 44 forloop : 44
forloop : 44 forloop : 44 forloop : 55
$1 : $1 : 44
forloop : 55 forloop : 55
$1 : $1 : 44
forloop : 66
$1 :
$IFS
$IFS
변수와 set
명령을 이용하여 스트링에서 필드를 분리해낼 수 있습니다.
#!/bin/sh
line="11:22:33:44:55"
set -f; IFS=: # globbing 을 disable
set -- $line # IFS 값에 따라 필드를 분리하여 positional parameters 에 할당
set +f; IFS=`echo " \n\t"`
echo number of fields = $#
echo field 1 = "$1"
echo field 2 = "$2"
shift 3
echo \$@ = "$@"
############# output #############
number of fields = 5
field 1 = 11
field 2 = 22
$@ = 44 55
이번에는 $*
를 이용한 join
#!/bin/sh
files="aaa.c bbb.c ccc.c"
set -- $files
join2() { local IFS=$1; shift; echo "$*" ;}
join_files=$( join2 ":" "$@" )
echo "$join_files"
############# output #############
aaa.c:bbb.c:ccc.c
Substring expansion
Substring expansion 은 bash 에서만 사용할수 있습니다.
$ set -- 11 22 33 44 55
$ echo ${@:3}
33 44 55
$ echo ${@:2:2}
22 33
Quiz
foo/bar/file.txt
파일을 mydir
디렉토리로 복사할 때 결과가
mydir/file.txt
가 아니라 경로를 포함해서 mydir/foo/bar/file.txt
가 될 수 있게
함수를 작성하는 것입니다.
이때 파일 이름에는 glob 문자를 사용할 수 있습니다.
cpd() {
if test $# -lt 2 -o "$1" == "-h" -o "$1" == "--help"
then
echo "Usage : cpd src/dir/files dest/dir" >&2
return 1
fi
local dir=${1%/}
if [[ $dir == */* ]]; then
dir=${@:$#}/${dir%/*}
else
dir=${@:$#} # ${@:$#} 는 마지막 인수가 되고
fi # ${@:1:$#-1} 는 마지막 인수를
mkdir -p "$dir" && cp -a "${@:1:$#-1}" "$dir" # 제외한 나머지 인수들이 된다.
}
$ cpd foo/bar/file.txt mydir
$ cpd foo/bar/*.txt mydir
# 위에서처럼 파일 이름에 glob 문자가 사용되면 명령이 실행될 때는
# cpd foo/bar/a.txt foo/bar/b.txt foo/bar/c.txt mydir 형태가 됩니다.
# 이때 ${@:$#} 값은 마지막 인수인 mydir 가 되고
# ${@:1:$#-1} 의 값은 mydir 를 제외한 나머지 인수들이 됩니다.
# 마지막 인수값은 ${@:$#} 대신에 indirection 을 이용해 ${!#} 로 나타낼수도 있습니다.
# 현재 전체 인수 개수가 3 개라면 : !# ---> $# ---> 3
다음 버전은 for 문을 이용해 각각 다른 경로를 가진 파일들도 처리합니다.
#
# cpd a/b/*.txt c/*.txt d/e/f dest/dir
#
cpd() {
if test $# -lt 2 -o "$1" == "-h" -o "$1" == "--help"
then
echo "Usage : cpd src/dir/files dest/dir" >&2
return 1
fi
local i file dest=${!#} dest_new
for (( i=1; i<$#; i++ )) do
file=${!i%%+(/)}
if [[ $file == */* ]]; then
dest_new=$dest/${file%/*}
else
dest_new=$dest
fi
mkdir -p "$dest_new" && cp -ai "$file" "$dest_new"
done
}
mvd() {
if test $# -lt 2 -o "$1" == "-h" -o "$1" == "--help"
then
echo "Usage : mvd src/dir/files dest/dir" >&2
return 1
fi
local i file dest=${!#} dest_new
for (( i=1; i<$#; i++ )) do
file=${!i%%+(/)}
if [[ $file == */* ]]; then
dest_new=$dest/${file%/*}
else
dest_new=$dest
fi
mkdir -p "$dest_new" && mv -i "$file" "$dest_new"
done
}
2 .
uftrace
명령을 이용해 실행파일을 trace 하려면 먼저 소스파일을 gcc -pg -g
옵션으로
다시 컴파일을 해야 하는데요.
직접 소스파일을 trace 해볼 수 있게 스크립트를 작성하는 것입니다.
이때 gcc 에 추가로 옵션을 줄수 있어야 하고 실행파일에도 옵션을 줄수 있어야 합니다.
$ cat uftrace.sh
#!/bin/bash
if (( $# == 0 )); then
echo "Error: file name required."
exit 1
fi >&2
file=$1
shift
opts=()
while [[ $1 != "--" && $# != 0 ]]; do
opts+=( "$1" )
shift
done
[[ $1 == "--" ]] && shift
gcc -pg -g "$@" "$file" &&
uftrace -a a.out "${opts[@]}"
-------------------------------------------------
$ cat fibo.c
#include <stdio.h>
#include <stdlib.h>
long fibo(int x)
{
if (x == 1) return 1;
if (x == 2) return 1;
return fibo(x - 1) + fibo(x - 2);
}
int main(int argc, char *argv[])
{
printf("%ld\n", fibo(atoi(argv[1])));
}
-------------------------------------------
# 1. uftrace.sh 스크립트의 첫번째 인수로 "fibo.c" 소스파일이 오고
# 2. 그다음 실행시 전달할 "10" 옵션 값들이 옵니다.
$ uftrace.sh fibo.c 10
55
# DURATION TID FUNCTION
1.163 us [140797] | __monstartup();
0.182 us [140797] | __cxa_atexit();
[140797] | main(2, 0x7ffc98ea68e8) {
107.842 us [140797] | atoi("10") = 10;
[140797] | fibo(10) {
[140797] | fibo(9) {
[140797] | fibo(8) {
. . .
# 3. gcc 에 추가로 옵션을 줄경우 '--' 이후에 적어주면 됩니다.
$ uftrace.sh fibo.c 10 -- -O2
. . .
3 .
함수 A 에 전달된 인수들을 실행 중에 함수 B 를 호출해서 처리하게 하려면 어떻게 할까요?
shell 함수는 호출될 때마다 자동으로 positional parameters 가 설정되므로
함수 A 가 실행 중에 B 를 호출한다고 하더라도 함수 B 에서는 함수 A 의
positional parameters 를 사용할 수가 없습니다.
따라서 이때는 함수 A 에 전달된 인수들을 "$@"
변수를 이용해서
함수 B 를 호출할 때도 전달해야 합니다.
#!/bin/bash
foo () {
echo "function foo()"
for (( i = 1; i <= $#; i++ )) do echo "\$$i : ${!i}"; done
bar # bar 함수를 호출
}
bar () {
echo "function bar()"
for (( i = 1; i <= $#; i++ )) do echo "\$$i : ${!i}"; done
}
foo 11 22 33
########### 실행 결과 ###########
$ ./test.sh
function foo()
$1 : 11
$2 : 22
$3 : 33
function bar() # foo 함수 실행중에 bar 함수를 호출했지만 bar 함수에서는
$ # foo 함수의 positional parameters 를 사용할 수 없다.
------------------------------------------------------------
# foo 함수를 다음과 같이 수정합니다.
foo () {
echo "function foo()"
for (( i = 1; i <= $#; i++ )) do echo "\$$i : ${!i}"; done
bar "$@" # bar 함수를 호출할 때 positional parameters 를 전달해야 한다.
}
########### 실행 결과 ###########
$ ./test.sh
function foo()
$1 : 11
$2 : 22
$3 : 33
function bar()
$1 : 11
$2 : 22
$3 : 33
4 .
다음과 같이 test.sh 명령에 인수가 전달되면 스크립트 내에서 $@
의 값은
Arch Linux
, Ubuntu Linux
, Suse Linux
, Fedora Linux
, *
가 되는데요.
이중에서 Suse Linux
원소를 삭제하려면 어떻게 할까요?
$ test.sh "Arch Linux" "Ubuntu Linux" "Suse Linux" "Fedora Linux" "*"
$ cat test.sh
echo 'number of "$@" :' $#
for v; do echo "$v"; done # 삭제전 $@ 값을 출력
echo "-------------------"
# for 문에서 순회시 사용되는 $@ 값은 shift, set 에의해 변경되는 $@ 값과 별도로 존재합니다.
for arg do shift # arg 값이 Arch 일때 shift 를 하면
[ "$arg" = "Suse Linux" ] && continue # $@ 값은 Ubuntu, Suse, Fedora, * 가 된다.
set -- "$@" "$arg" # set 이후 $@ 값은 Ubuntu, Suse, Fedora, *, Arch 가 된다.
done
echo 'number of "$@" :' $#
for v; do echo "$v"; done # 삭제후 $@ 값을 출력
-----------------------------------------------------
$ ./test.sh
number of "$@" : 5
Arch Linux
Ubuntu Linux
Suse Linux
Fedora Linux
*
-------------------
number of "$@" : 4
Arch Linux
Ubuntu Linux <---- Suse Linux 가 삭제되었다.
Fedora Linux
*
5 .
이번에는 foo
함수에서 생성된 $@
값을 bar
함수에 전달해서 수정하게 한 후에
결과를 다시 opts
변수를 통해 foo
함수에 전달하는 것입니다.
이때 공백이 포함된 인수가 있을 경우 인수가 분리되지 않도록 해야 합니다.
#!/bin/bash
bar() {
# foo 함수에서 전달받은 인수들 중에서 "Suse Linux" 원소를 삭제하고
IFS=$'\n'
set -- $( for v; do [ "$v" != "Suse Linux" ] && echo "$v"; done )
unset -v IFS
# 결과를 foo 함수의 local 변수인 $opts 에 설정
# 이때 인수들 중에 공백이나 quotes 이 사용될수 있으므로 printf "%q " 를 사용해야 합니다.
# 그러면 Arch\ Linux Ubuntu\ Linux Fedora\ Linux 와 같이 설정됩니다.
if [ $# -gt 0 ]; then opts=`printf "%q " "$@"`; fi
# 또는 다음과 같이 매개변수 transformation 을 이용: ${parameter@Q}
# if [ $# -gt 0 ]; then opts=${@@Q}; fi
}
foo() {
local opts
echo 'number of "$@" :' $#
for v; do echo "$v"; done # 삭제전 "$@" 값을 출력
echo "--------------------"
bar "$@"
# bar 함수가 설정한 $opts 값을 이용해 다시 $@ 값을 설정
# bar 함수에서 printf "%q " 를 이용해 $opts 값을 설정하였으므로
# Arch Linux 는 두 개의 인수가 되지 않고 하나의 인수가 됩니다.
eval set -- $opts
echo 'number of "$@" :' $#
for v; do echo "$v"; done # 삭제후 "$@" 값을 출력
}
foo "Arch Linux" "Ubuntu Linux" "Suse Linux" "Fedora Linux"
------------------------------------------------------------
$ ./test.sh
number of "$@" : 4
Arch Linux
Ubuntu Linux
Suse Linux
Fedora Linux
--------------------
number of "$@" : 3
Arch Linux
Ubuntu Linux
Fedora Linux