-
Shell scriptLinux/development tool 2023. 7. 10. 02:20
글의 참고
- https://hbase.tistory.com/249
- https://velog.io/@alirz-pixel/linux-bashcompletion
글의 전제
- 밑줄로 작성된 글은 강조 표시를 의미한다.
- 그림 출처는 항시 그림 아래에 표시했다.
글의 내용
- 쉘 정수 표현
" 쉘 스크립트에서 정수 표현은 특별한 기호를 사용해야 한다. 예를 들어, 변수 2개를 그냥 더하면, 문자열 2개가 더해지는 것과 같은 효과를 준다. 실제 수학적 연산이 되려면 `$(( ))` 안에 들어와 줘야한다. 그냥 더할 경우, 문자열 합치기가 된다.
KERN16_PADD=$(($KERN16_SIZE+$SECTOR_SIZE)) ; 512+2 = 514 KERN16_PADD=$KERN16_SIZE+$SECTOR_SIZE ; 512+2 = 512+2
" 연산 뿐만이 아니라, 특정 명령어의 파라미터로 정수를 줘야 할 때도 `(( ))` 기호를 감싸야 한다. 첫 번째, 문장은 `truncate` 명령어에서 숫자를 줘야한다는 에러를 뿜는다. 올바르게 사용하려면 `$(( ))`로 감싸서 처리해주면 된다.
truncate -s $KERN16_ALIGN_SIZE temp/bl.img ; X truncate -s $(($KERN16_ALIGN_SIZE)) temp/bl.img ; O
" 쉘 스크립트에서 수학적 연산에 `let`, `expr` 등도 사용하지만, 나는 `$(( ))` 혹은 `(( ))`이 가장 편하다.
- $(( )) 과 (( )) 차이
" $(( ))은 값을 반환한다. `(( ))`은 내부에서 수학적 연산만 한다. 그래서 반환값이 없다.
truncate -s (($KERN16_ALIGN_SIZE)) temp/bl.img ; X truncate -s $(($KERN16_ALIGN_SIZE)) temp/bl.img ; O
" 명령어의 인수로 계산 결과를 주려면 $(( ))을 사용해야 한다. IF 문에서도 마찬가지다. `(( ))`은 내부에서 연산만하고 결과를 반환하지 않기 때문이다. 그렇면 `(( ))`만 사용하는 경우를 보자.
if [ $(($KERN16_ALIGN_SIZE%$CHS_MAX_SECS)) -ne 0 ] ; O if [ (($KERN16_ALIGN_SIZE%$CHS_MAX_SECS)) -ne 0 ] ; X
" 아래 같은 경우, `(( ))` 내부에서 수학적 연산(`+=`)만 하고 있다. 이럴 경우, `(( ))`을 사용하면 된다. 만약, 저 때 `$(( ))`을 사용하면 `command not found` 에러가 발생한다.
if [ $(($KERN16_ALIGN_SIZE%$CHS_MAX_SECS)) -ne 0 ] then ((KERN16_ALIGN_SIZE+=$SECTOR_SIZE)) ; O $((KERN16_ALIGN_SIZE+=$SECTOR_SIZE)) ; X else break fi done
" 주의할 점은 $(( )) 은 내부에 내부에서 진행된 연산을 명령어로 수행하기 때문에 주의가 필요하다. 애초에 `$( )` 표현자체가 괄호안에 명령어를 수행한다는 의미가 되기 때문이다.
- Parameter Substitution [참고1]
" 아래보면 shell script 변수에서 특정 문자를(#, %) 를 통해서 변수의 값을 조정이 가능하다. 예시는 아래와 같다.
1. # - # 앞에 존재하는 문자열의 맨 앞 부분의 일치하는 문자열을 제거한다.
2. % - % 앞에 존재하는 문자열의 맨 뒷 부분의 일치하는 문자열을 제거한다.#!/bin/bash TEST="yohda-love-123" TEST1="12yohda-love-123456" TEST2="love" RET1=${TEST#yohda} RET2=${TEST%123} RET3=${TEST1#yohda} RET4=${TEST1%123} echo "TEST - ${TEST}, after - \${TEST#yohda-} ${RET1}" # TEST - yohda-love-123, after - ${TEST#yohda-} -love-123 echo "TEST - ${TEST}, after - \${TEST%123} ${RET2}" # TEST - yohda-love-123, after - ${TEST%123} yohda-love- echo "TEST1 - ${TEST1}, after - \${TEST1#yohda} ${RET3}" # TEST1 - 12yohda-love-123456, after - ${TEST1#yohda} 12yohda-love-123456 echo "TEST1 - ${TEST1}, after - \${TEST1%123} ${RET4}" # TEST1 - 12yohda-love-123456, after - ${TEST1%123} 12yohda-love-123456
- $( )
" `$( )`는 명령어를 수행하는 표현법이다. `COMMAND` 는 이제는 예전 표현법이 됬다.
- new : ${compgen -W "${TVS}"}
- old : `compgen -W "${TVS}"`- 달라 사인($)
" 값을 가져올 때,
Once you have stored a value into a variable, how do you get the value back out? You do this in the shell by preceding the variable name with a dollar sign ($). If you wanted to print the value stored in the count variable to the screen, you would do so by entering the command "echo -n $count". If you omitted the $ from the preceding command, the echo command would display the word count on-screen. Also, here "-n" is an option for echo not to append a carriage return to the end of the line.
- http://linuxsig.org/files/bash_scripting.html" 변수에 값을 할당할 때,
You can assign a value to a variable simply by typing the variable name followed by an equal sign and the value you want to assign to the variable. For example, if you wanted to assign a value of 5 to the variable count, you would enter the following command "count=5". With the bash syntax for setting a variable, you must make sure that there are no spaces on either side of the equal sign. Notice that you do not have to declare the variable as you would if you were programming in C or Pascal. This is because the shell language is a non-typed interpretive language. This means that you can use the same variable to store character strings that you use to store integers. You would store a character string into a variable in the same way that you stored the integer into a variable. For example "name=Garry " is also completely valid.
- http://linuxsig.org/files/bash_scripting.html- complete 명령어[참고0 참고1 참고2 참고3]
" `complete` 명령어를 사용하면, 자동 완성 기능을 만들 수 있다. 아래와 같이 `complete -F _test test` 로 명시하면, shell 에서 `test` 까지만 입력한 후, [TAB] 키를 누르면, _test() 함수가 호출된다. 즉, shell 에서 `auto completion test` 를 출력한다는 소리다. 그런데, 이게 내장 명령어는 아닌지 `bash_completion` 이라는 package 를 설치해야 하는 것 같다(이 부분은 공식 문서를 본게 아니라는 점을 참고바람).
// https://hbase.tistory.com/249 #!/bin/bash _test() { echo 'auto completion test' } complete -F _test test
" complete 명령어와 함께 사용되는 자동 완성 변수들이 존재한다.
# https://velog.io/@alirz-pixel/linux-bashcompletion foo --version --help --horizon \ | \ \_ $1 |_$3 \_$2 (command) (previous) (current)
" 위 코드에서 볼 수 있다시피, command($1), pre($3), cur($2) 만 존재하기 때문에 `--version` 파라미터(현재를 기준으로 이전 이전 값) 는 저장할 수 가 없다.
- COMP_WORDS : 현재까지 입력한 명령 라인의 인수들을 값으로 가지고 있는 array 변수이다. 위의 예시에서는
COMP_WORDS[0]=foo, COMP_WORDS[1]=--version, COMP_WORDS[2]=--help, COMP_WORDS[3]=--horizon
가 된다.
- COMP_CWORD : 현재 커서가 위치한 인수의 index를 나타내며, 위에서 적은 예시에서는 3이 된다.
# https://velog.io/@alirz-pixel/linux-bashcompletion $2 == ${COMP_WORDS[COMP_CWORD]} $3 == ${COMP_WORDS[COMP_CWORD - 1]}
- COMPREPLY : tab 키를 눌렀을 때, 보여지는 내용을 담는 array 변수. 값이 출력될 때는 자동으로 중복이 제거되고, soft 되어 출력된다.- export & local 키워드
" shell script 안에서 `export` 키워드는 환경 변수를 설정해주는 키워드다. 저렇게 export 로 써주면 좋은 점은 자식 프로세스에서도 export 로 설정된 환경 변수에는 access 가 가능하다. 예를 들어, `export NAVI="V=s"` 하면, 자식 프로세스에서도 LUNCH_MENU_CHOICES 에 access 가 가능하다. 그러나, ` NAVI="V=s" ` 로 선언하면, 자신만 access 할 수 있다. local 키워드는 bash 에서 사용하는 지역 변수를 선언할 때, 사용하는 키워드다. 즉, love() 함수안에서만 vendor 변수를 사용할 수 있다. 참고로, NAVI 는 환경 변수이므로, 전역 변수와 동일하게 다른 함수에서도 access 가 가능하다.
function love { .... export NAVI="V=s" local vendor .... }
- [] 와 test [참고1]
" shell script 에서 아래와 같이 if 가 생략되고, 대괄호안에 조건문들이 오는 경우가 있다. 이 때는 그냥 간단하게 대괄호안에 결과값이 대괄호를 통해서 boolean 으로 치환된다고 보면 된다. 아래 2 개는 완전히 동일한 명령어다.
test -z $T [ -z "$T" ]
- File arguments
" tese `expression` or [ expression ] 시에 사용 arguments 가 상당히 중요한데, 요약하면 다음과 같다.
Argument Return `true` if the file -d 디렉토리라면 -f regular file 이라면 -e 파일이 존재하면 -s 사이즈가 0 보다 크면 -x 실행이 가능하면 -w 쓰기가 가능하면 -r 현재 프로세스에서 해당 파일을 읽기가 가능하면 - String arguments
" 아래에서 `=` 와 `==` 는 문자열 비교해서 동일한 기능을한다. 주의할 점이 있다. = 와 == 이 동일한 것은 bash 에서만 이다. 만약, sh 를 사용한다면 = 와 == 는 다른 의미로 사용된다[참고1].
-n String1 String1 의 길이가 non-zero -z String1 String1 의 길이가 zero String1 = String2 String1 과 String2 는 일치한다. String 1 == String 2 String1 과 String2 는 일치한다. String1 != String2 String1 과 String2 는 동일하지 않다. String1 이 값이 만약 true 라면, String1 은 null string 이 아니다. - Number arguments
integer1 -eq integer2 integer1 과 integer2 는 수학적으로 일치한다. integer1 -ne integer2 integer1 과 integer2 는 수학적으로 일치하지 않는다. integer1 -gt integer2 integer1 이 integer2 보다 수학적으로 더 크다. integer1 -ge integer2 integer1 이 integer2 보다 수학적으로 더 크거나 같다. integer1 -lt integer2 integer1 이 integer2 보다 수학적으로 작다. integer1 -le integer2 integer1 이 integer2 보다 수학적으로 작거나 같다. - Operators
! 부정 연산자 -a binary AND operator -o binary OR operator " 예를 들어, 아래 조건문은 어떻게 해석해야 할까? 이 문장은 $SPL 가 empty value 이거나 $TOP_DIR/yohda/version-2.0/$SPL/Makfile 이 존재하지 않을 경우, 참이 된다. 아래에서 `-o` 는 `||` 로 해석하면 좋을 것 같다.
if [ x"$SPL" == x -o ! -f $TOP_DIR/yohda/version-2.0/$SPL/Makfile ]; then
" 그렇다면, 다음 코드는 어떻게 해석할까? "$1" 파일의 size 가 0 보다 크지 않다면, true 가 되서 에러 문구를 출력한다. size 가 0 보다 크지 않다는 건 2 가지 의미를 갖는다. 파일 자체가 없거나 파일은 존재하는데 내용이 없는 경우다.
# https://en.wikipedia.org/wiki/Test_%28Unix%29 if test ! -s "$1" then echo $1 does not exist or is empty. fi
" 전달된 인자의 개수($#) 가 2 개가 안되거나, "$1" 파일이 존재하지 않는다면, 현재 shell script 를 종료한다.
# https://en.wikipedia.org/wiki/Test_%28Unix%29 if [ "$#" -lt 2 ] || ! [ -e "$1" ] then exit fi
- x 비교문 [참고1]
" shell script 를 보다보면, 아래와 같은 조건문들을 만나게 되는 경우가 있다. 여기서 x 는 shell script 의 특별한 키워드는 아니다. 말 그대로 그냥 `x` 다. 이렇게 사용하는 이유는 일부 shell or old shell 에서는 empty value 비교가 어렵기 때문이다. 그렇기 때문에, 접두사로 x 를 붙이면, empty value 일 경우, [ x = x ] 혹은 [ x = x3] 되어 true 혹은 false 에 대한 조건을 본인이 만들 수 가 있다. 일반적으로, [ x$1 = x ] 과 같이 사용하면, $1 가 empty value 일 때 true 가 되고, [ x"$B" = x"3" ] 인 경우는 $B 가 empty value 일 때, false 가 된다. 즉, 입맛에 따라 적절하게 사용할 수 있다는 뜻이다.
[ x$1 = x ] or if [ x"$B" = x"3" ]; then ...
- compgen 명령어 [참고1]
" compgen 명령어는 자동완성 이름을 생성하는데 사용된다. 앞서 소개한 complete 명령의 옵션들을 거의 동일하게 사용할 수 있다. 한가지 유용한 기능은 마지막에 `-- ${word}` 를 인수로 주면 word 와 매칭되는 단어들만 선택된다. 그러므로, 주로 COMPREPLY 배열에 값을 저장하는 용도로 사용된다.
# 마지막에 '-- word' 인수를 주면 매칭되는 단어들만 나온다 $ compgen -W 'a111 b222 b333 c444' -- b # wordlist 들 중에서 `b` 에 매핑되는 words 를 출력 b222 b333 ------------------------------------- COMPREPLY=( $(compgen -d -- "$cur") ) # 디렉토리 이름을 자동완성 단어로 사용
- export & local 키워드
" `export PATH=...` 로 PATH 를 추가할 때 주의 사항이 있다. 예를 들어, 나같은 경우 `/usr/bin/script` 라는 실행 파일이 있었다. 그런데, `/home/yohda/script` 파일 또한 PATH 에 설정해서 어디서나 사용하고 싶었다. 이럴 경우, PATH 에 어떻게 추가해야 할까? 아래와 같이 `/home/yohda` 를 $PATH 보다 앞에 선언하면 된다. 즉, PATH 에 앞쪽에 선언된 파일이 뒤쪽보다 우선 순위가 높다.
export PATH=/home/yohda/:$PATH
" 만약, 아래와 같이 `/home/yohda/` 가 $PATH 의 뒤쪽에 오면 우선 선위가 `/usr/bin/script`에 밀리게 된다.
export PATH=$PATH:/home/yohda/
" 배열을 선언하는 코드는 다음과 같다.
# http://blog.redjini.com/282 array_name_1=("value 1" "value 2" "value 3") array_name_2=(1 2 3)
" 배열을 참조하는 방법은 다음과 같다.
# http://blog.redjini.com/282 array_name=("value 1" "value 2" "value 3") echo "array_name[0] = ${array_name[0]}" #print array_name[0] echo "array_name[2] = ${array_name[2]}" #print array_name[2] echo "array_name[*] = ${array_name[*]}" #print array_name all item echo "array_name[@] = ${array_name[@]}" #print array_name all item echo "array_name index = ${!array_name[@]}" #print array_name index number echo "array_name size = ${#array_name[@]}" #print array_name size echo "array_name[0] size= ${#array_name[0]}" #print array_name[0] size
" 위에 내용에 대한 출력 결과는 다음과 같다.
# http://blog.redjini.com/282 array_name[0] = value 1 array_name[2] = value 3 array_name[*] = value 1 value 2 value 3 array_name[@] = value 1 value 2 value 3 array_name index = 0 1 2 array_name size = 3 array_name[0] size= 7
" 기존 배열에 새로운 요소를 추가하는 방법은 다음과 같다.
$ array=("hoge" "fuga" "foo" "bar") $ array=(${array[@]} "end") $ echo ${array[@]} # hoge, fuga, foo, bar, end
- set 명령어
" `set -e` 를 shell script 제일 앞에 선언할 경우, 스크립트 실행 도중에 에러가 발생하면 그 즉시 종료한다. 만약, `set -e` 를 설정하지 않으면 에러가 발생하더라도 종료되지 않는다.
" shell script 를 실행할 때, 여러 가지 옵션등을 전달하게 되는데 이 때 해당 옵션들을 파싱해주는 기능을 한다. 템플릿처럼 아래 형태를 아래서 옵션 인자 다음에 `:` 가 오면 인자를 받는다는 뜻이다. 즉, 옵션 a 는 인자를 받고 나머지 옵션들은 인자를 받지 않는다.
function getops_test { .... while getotps "a:sub" option do case $option in a) whatis=$OPTARG ;; # all build(boot-loader, uboot) u) ;; # uboot build b) ;; # boot-lodaer build s) ;; # sd card boot build esac done .... }
- 파라미터(인자 및 매개변수)
" shell script 를 실행할 때, 기본적으로 shell script 에서 제공하는 변수들이 존재한다. 바로 `매개 변수` 다. 내용 자체가 간단하므로 예시로 대체한다.
# yohda.sh echo $# echo $0, $1, $2, $3, $4, $5 $ ./yohda.sh 1 2 3 3 ./yohda.sh, 1, 2, 3, ,
- eval
" 문자열을 명령어로 실행하도록 한다.
- 여러 줄 주석 [참고1]
" shell script 에서 여러 줄을 주석하기 위해서 다양한 방법이 있는 것으로 보이는데, `bash` 를 기준으로 아래와 같이 작성하면 여러 줄 주석이 가능한것을 확인했다. 아래 yohda 라고 표시된 부분을 자신이 정할 수 있는 것으로 보인다. 아래에서 `:` 뒤에 ' 은 한 칸 띄어쓰기하고 작성해야 한다.
: ' ... contents ... '
" 위에 주석은 아래 테스트 환경을 전제로 한다.
$ cat /etc/os-release NAME="Ubuntu" VERSION="18.04.6 LTS (Bionic Beaver)" ID=ubuntu ID_LIKE=debian PRETTY_NAME="Ubuntu 18.04.6 LTS" VERSION_ID="18.04" HOME_URL="https://www.ubuntu.com/" SUPPORT_URL="https://help.ubuntu.com/" BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/" PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy" VERSION_CODENAME=bionic UBUNTU_CODENAME=bionic $ bash --version GNU bash, version 4.4.20(1)-release (x86_64-pc-linux-gnu) Copyright (C) 2016 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software; you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law.
- 함수 return
" 일반적으로 shell script 에서 함수의 반환값은 #? 를 통해서 얻어야 한다. 중요한 건 바로 직전에 실행된 함수의 결과만 #? 으로 얻을 수 있다[참고1]. 일반적인 프로그래밍 언어와 같이 `변수 = 함수()` 처럼 사용되지 않는다는 것이다. 그러나, 이런 형태가 가능한 경우가 있다. 바로 함수 내부에서 echo 로 반환하면 된다[참고2].
'Linux > development tool' 카테고리의 다른 글
[운영체제 만들기] xxd (0) 2023.08.03 [GIT] - git diff를 통한 patch 파일 생성 및 적용 (0) 2023.08.03 dd (0) 2023.06.10 [개발 도구] objdump (0) 2023.06.10 [개발도구] - inline assembly (0) 2023.06.07