검색 엔진의 방문이 늘어나고 있군...

Posted
Filed under 쉘 스크립트
참조 원문 : Introduction to awk
관련 글 : awk를 이용한 검색, awk에서 변수 사용, 6가지 awk 트릭

  awk는 패턴을 찾아 그와 관련된 문자열을 처리하는 유틸리티로 텍스트 파일에서 정보를 찾아 필요한 형식으로 재생성, 파일의 서식 변경, 데이터베이스 생성과 데이터를 이용한 수학 계산에 사용합니다. awk라는 이름은 프로그램 제작자들의 이름인 Aho, Weinberger, Kernighan의 앞글자들을 따서 만든 겁니다. 그 외에 awk의 최신 버전에 해당하는 nawk라는 것이 있고 GNU 버전인 gawk라는 것도 있습니다. 리눅스의 경우 awk 명령어가 심볼릭 링크를 통해 gawk를 가리키고 있는 경우가 많습니다. 참고로 우분투의 경우 awk가 심볼릭 링크를 통해 mawk(mike's awk)라는 프로그램으로 링크되어 있는데 사실상 둘이 다를 게 거의 없기 때문에 신경쓰지 않아도 됩니다.

  awk는 표준 입력(=stdin, 키보드로부터 받은 입력), 파일, 다른 프로세스의 출력으로부터 데이터를 받습니다. 데이터를 받으면 입력한 패턴과 일치하는 줄을 찾습니다.


awk 사용
awk [ -F<char> ] {pgm} | { -f <pgm_file> } [ <vars> ] [ - | <data_file> ]
  • char: 필드 구분 문자
  • pgm: 실행할 명령어
  • vars: 변수
  • data file: 입력 데이터 파일
  기본적으론 공백이나 탭이 필드 구분 문자로 사용되지만 -F 옵션을 사용하면 다른 문자를 필드 구분 문자로 사용할 수 있습니다. 예를 들어 필드가 콜론(:)으로 나눠져 있을 경우 아래와 같이 사용합니다.
awk -F:

파일 내용을 입력
  아래는 awk를 이용하여 특정 파일에서 지정한 패턴을 찾는 문법과 실제 사용 예로 ps 명령어의 출력 결과를 텍스트 파일로 만든 후 awk를 이용해 "apache"라는 문자열이 있는 줄을 검색하는 것을 보여주고 있습니다.
문법: awk 'pattern' filename

$ ps aux > processes
$ awk '/apache/' processes
apache    2206  0.0  1.0 207376  3972 ?   S   22:20   0:00 /usr/sbin/httpd
apache    2207  0.0  1.0 207376  3972 ?   S   22:20   0:00 /usr/sbin/httpd
...생략...
  아래는 패턴 대신 액션을 사용한 예입니다. 출력 결과가 위와 다른데 그 이유는 "$1"을 사용하여 모든 줄의 첫 번째 필드를 출력하게 했기 때문입니다. 필드 구분 문자를 따로 지정하지 않았기 때문에 공백을 필드 구분 문자로 사용합니다.
문법: awk 'action' filename

$ ps aux > processes
$ awk '{print $1}' processes
root
root
...생략...
  아래는 패턴과 액션을 섞어서 처음 결과에서 첫 번째 필드만 뽑아내는 문법과 예입니다.
문법: awk 'pattern {action}' filename

awk '/apache/{print $1}' processes
apache
apache
apache
2010/12/09 13:55 2010/12/09 13:55
Posted
Filed under 쉘 스크립트
참조 원문 : Linux: Using Remote Wakeup (Wake on LAN)
관련 글 : [시스템] 리눅스에서 WoL 작동시키기

  뭐, 가치가 있는 포스팅인지 모르겠으나 재미있어 보여서 일단 질러봅니다. 대략 아래와 같은 시나리오입니다.

  "재택 근무자들을 위해 WoL용 리눅스 서버를 만들려고 한다. 재택 근무자들은 이 서버를 통해 회사에 있는 자신의 컴퓨터를 부팅할 수 있다. 재택 근무자들은 리눅스에 대해 모르기 때문에 당신은 Putty 같은 클라이언트 프로그램과 그에 맞는 접속 세션 정보가 담긴 파일을 제공하여 쉽게 서버로 접속할 수 있게 해야 한다. 재택 근무자들이 서버에 접속 후 'wol'이라는 이름의 스크립트를 실행하여 원하는 컴퓨터를 킬 수 있게 할 것이다."

  뭐, 별도로 서버를 하나 세워야 한다는 측면에서 이미 상당히 의미 없어보이는 시나리오지만 어쨌든 아이디어 자체는 재미있어 보입니다. 일단 이 일을 실현할 'wol'이라는 스크립트 파일의 내용은 아래와 같습니다.
#!/bin/bash
clear
wolmenu="wol.menu"
woldata="wol.data"
wolloc="`dirname \"$0\"`/"
if [ ! -f $wolloc$wolmenu ]
then
     echo Cannot find my menu file. My files should be in $wolloc.
     elif [ ! -f $wolloc$woldata ]
     then
          echo Cannot find my data file. My files should be in $wolloc.
     else
          cat $wolloc$wolmenu
          echo;echo Type the number of the PC to awaken or c to cancel and press Enter:
          read n
          case $n in
               c) exit;
               ;;
               C) exit;
               ;;
               *) echo Waking up `grep ^$n $wolloc$wolmenu`.;
               ipsubnet=`grep ^$n $wolloc$woldata|cut -d ' ' -f 3`;
               hwaddress=`grep ^$n $wolloc$woldata|cut -d ' ' -f 2`;
               echo The command running is - wakeonlan -i $ipsubnet $hwaddress;
               wakeonlan -i $ipsubnet $hwaddress;
               ;;
     esac
fi
  작가 스스로도 시인했듯이 스크립트가 상당히 허접합니다. 입력 값 검사 같은 루틴이 전혀 없고 WoL이 성공했는지 핑 같은 것으로 검사하는 루틴도 없습니다. 뭐, 그건 넘어가자고 하니 넘어갑시다.

  스크립트를 살짝쿵 훑어봤다면 wol.menu와 wol.data라는 파일이 별도로 사용된다는 것을 알 수 있습니다. 이름에서도 알 수 있지만 wol.menu 파일은 이 wol이라는 스크립트를 실행했을 때 메뉴로 출력할 일반 텍스트 파일이라는 것을 알 수 있습니다. 대충 내용은 아래와 같이 작성하면 되겠죠.
Number PC Name             HW Address         IP Address
====== =================== ================== ================
1      ACCOUNTING          00:11:22:33:44:50  192.168.1.10
2      FINANCE             00:11:22:33:44:51  192.168.1.11
3      MANAGER             00:11:22:33:44:52  192.168.1.12
  wol.data 파일은 실제 WoL의 대상이 될 머신의 MAC 주소와 해당 서브넷의 브로드캐스트 주소를 담을 파일입니다. 내용은 아래와 같은 형식입니다.
1 00:11:22:33:44:50 192.168.1.255
2 00:11:22:33:44:51 192.168.1.255
3 00:11:22:33:44:52 192.168.1.255
  스크립트 파일의 내용을 봤을 때 대충 어떻게 돌아가는지 바로 파악이 되실겁니다. 출력된 메뉴를 보고 번호를 누르면 wol.data 파일에서 해당 번호를 찾아 대상 서브넷의 대상 MAC 주소를 향해 매직 패킷을 날리는 것이죠. 좀 개조하면 wol.data 파일이 필요 없겠군요. 브로드캐스트 주소 때문에 많이 개조해야 할라나...



  이거 말고도 아래와 같은 시나리오도 있군요.

  "월요일~금요일에 한하여 06:30에 특정 컴퓨터들이 켜지게 만들고 싶다. 출근해서 부팅하기 귀찮거든."

  ...좀 어이없지만 어쨌든 아이디어는 재미있습니다. 켜지게 만들려는 컴퓨터들이 있는 서브넷에 항상 켜 있는 리눅스 컴퓨터가 있다면 cron에 아래와 같은 엔트리를 집어넣어 쉽게 위 시나리오를 해결할 수 있습니다.
30 6 * * 1-5 wakeonlan -i 192.168.1.255 -f /home/user/scripts/autowol.data
  물론 IP는 적절히 자신의 서브넷에 맞는 브로드캐스트 주소로 적절히 고쳐야겠죠? 안타깝지만 여기서도 별도의 파일을 사용해야 합니다. 위에서 autowol.data라는 파일을 사용하고 있는데 WoL의 대상 MAC 주소를 담고 있는 파일로 내용은 아래와 같습니다.
00:11:22:33:44:50
00:11:22:33:44:51
00:11:22:33:44:52
00:11:22:33:44:53
00:11:22:33:44:54

  지금까지 쓸데없어 보이는 WoL 스크립트 예제에 대해 살펴봤습니다. 근데 또 모르죠. 설마 이런 스크립트가 필요한 날이 언젠가 올지도...

2010/10/29 20:08 2010/10/29 20:08
Posted
Filed under 쉘 스크립트
참조 원문 : Single vs. Double Quotes in Bash

  배시 쉘을 쓰다보면 백 쿼트(`), 싱글 쿼트('), 더블 쿼트(")의 기능이 햇갈리기 마련입니다. 특히 싱글 쿼트와 더블 쿼트는 똑같이 문자열을 처리하기 위해 존재하지만 둘 사이에는 작은 차이가 있고 그 작은 차이도 파고들면 복잡해집니다.

  백 쿼트(`)는 그 안에 있는 명령어의 실행 결과(정확히는 표준 출력-stdout-)를 다시 입력으로 바꾸는 기능을 가지고 있습니다. 주로 변수에 어떤 명령어의 출력 결과를 집어넣을 때 사용합니다. 말로 설명하면 어렵지만 아래의 예로 쉽게 이해할 수 있으리라 생각합니다.
mirashi@myservlab:~$ echo Today is `date +%A`
Today is Tuesday

mirashi@myservlab:~$ uname
Linux
mirashi@myservlab:~$ UNAME_RET=`uname`
mirashi@myservlab:~$ echo $UNAME_RET
Linux

  이제 싱글 쿼트(')에 대해 알아보겠습니다. 두 싱글 쿼트 사이에 있는 문자열은 어떤 해석도 거치지 않고 문자 그대로 인식합니다. 아래의 예를 봅시다.
mirashi@myservlab:~$ name="Juliet Kemp"
mirashi@myservlab:~$ echo "$name"
Juliet Kemp
mirashi@myservlab:~$ echo '$name'
$name
  이렇듯 싱글 쿼트는 변환이고 해석이고 뭐고 가차 없이 문자열을 있는 그대로 받아들입니다. 참 솔직한 녀석이 아닐 수 없습니다. 그러므로 문자열에 특수 문자가 많을 때 유용합니다. 물론 같은 이유로 변수를 다루는 부분에서 쓰면 영 좋지 않겠죠?


  더블 쿼트(")는 좀 더 유연한 녀석입니다. 안에 있는 문자열에서 몇 가지 특수 문자를 재해석합니다. 재해석하는 문자로는 공식적으로 달러($), 백쿼트(`), 그리고 이스케이프(\ 로 시작) 문자가 있습니다만...디폴트 상태의 배시 쉘이라면 여기에 느낌표(!)가 추가됩니다. 여기에 대한 이야기는 조금 있다가 하도록 하겠습니다.

  더블 쿼트는 문자열 중간에 공백이 있을 때, 변수와 함께 사용할 때, 명령어의 인수로 사용할 문자열에 공백 문자가 포함되어 하나의 인수가 둘로 인식되는 것을 막을 때 유용합니다. 셋 중 앞의 2개는 바로 위에 있는 싱글 쿼트 예제의 첫 번째 줄과 두 번째 줄에서 차례대로 볼 수 있습니다. 아래는 세 번째 내용을 설명하기 위한 예제입니다.
mirashi@myservlab:~$ mkdir my directory
mirashi@myservlab:~$ ls -l
drwxr-xr-x 2 mirashi mirashi 4096 2010-10-21 19:38 directory
drwxr-xr-x 2 mirashi mirashi 4096 2010-10-21 19:38 my
mirashi@myservlab:~$ mkdir "my directory"
mirashi@myservlab:~$ ls -l
drwxr-xr-x 2 mirashi mirashi 4096 2010-10-21 19:38 directory
drwxr-xr-x 2 mirashi mirashi 4096 2010-10-21 19:38 my
drwxr-xr-x 2 mirashi mirashi 4096 2010-10-21 19:38 my directory
  전자의 경우 my와 directory라는 2개의 디렉토리가 만들어졌지만 후자는 공백 문자가 포함된 my directory라는 디렉토리가 만들어졌습니다. 그렇다면 이번에는 변수와 함께 사용할 때 어떻게 유용한지 볼까요?
mirashi@myservlab:~$ NameToRemove="my directory"
mirashi@myservlab:~$ rmdir "$NameToRemove"
mirashi@myservlab:~$ ls -l
drwxr-xr-x 2 mirashi mirashi 4096 2010-10-21 19:38 directory
drwxr-xr-x 2 mirashi mirashi 4096 2010-10-21 19:38 my
mirashi@myservlab:~$ rmdir $NameToRemove
mirashi@myservlab:~$ ls -l
mirashi@myservlab:~$
  둘의 차이가 보이십니까? 변수를 인수로 사용할 때 변수명을 더블 쿼트로 감싸면 변수의 내용 전체가 하나의 인수로 인식되는 것을 볼 수 있습니다. 변수의 내용을 변수명 대신 그대로 대입해보면 위 내용을 쉽게 이해할 수 있으리라 생각합니다. 또 다른 예로 아래와 같은 경우가 있습니다.
mirashi@myservlab:~$ ls
abcdef
mirashi@myservlab:~$ dpkg-query -l abc*
No packages found matching abcdef.
mirashi@myservlab:~$ dpkg-query -l "abc*"
No packages found matching abc*.
  사용자가 있는 디렉토리에는 abcdef라는 이름의 파일이 있습니다. 이 상태에서 abc라는 이름으로 시작하는 패키지를 검색하기 위해 dpkg-query -l abc* 라고 명령어를 실행했습니다. 그랬더니 abc*가 확장되어 abcdef로 변한 다음 dpkg-query 명령어가 실행되는 것을 볼 수 있습니다. 그래서 abc*를 더블 쿼트로 묶어 실행했더니 의도대로 명령어가 실행되는 것을 볼 수 있습니다.


  아까 더블 쿼트에서 달러($), 백쿼트(`), 그리고 이스케이프 문자 외에 느낌표(!)도 재해석되는 문자에 포함된다고 했는데 사실 엄밀히 따지자면 더블 쿼트와는 관계가 없습니다. 배시 쉘에서 느낌표는 '히스토리 익스팬션(History Expansion)'이라는 것의 디폴트 문자로 쓰이는데 이것은 배시 쉘의 히스토리 기능을 이용한 일종의 명령어 단축 실행 기능이라고 할 수 있습니다. 대충 아래와 같은 기능입니다.
mirashi@myservlab:~$ echo "history expansion"
history expansion
mirashi@myservlab:~$ !e
echo "history expansion"
history expansion
  문제는 이 히스토리 익스팬션 문자라는 것은 백슬래시(\)와 싱글 쿼트(')로만 문자 자체로서 표시(Quote)할 수 있다는 겁니다. 그럼 혹자는 '느낌표 앞에 백슬래시를 쓰면 되겠네?'라고 생각하겠지만 아래처럼 그 방법은 안 통합니다.
mirashi@myservlab:~$ echo "hi\!"
hi\!
  느낌표가 히스토리 익스팬션 문자로 쓰이는 것은 막았으나 더블 쿼트에서 재해석되는 문자는 달러($), 백쿼트(`), 이스케이프 문자 밖에 없기 때문에 백슬래시도 그대로 출력되어 버립니다. 그러면 느낌표를 어떻게 해야 출력할 수 있을까요? 혹자는 아래와 같은 해결 방안을 제시할 수도 있습니다.
mirashi@myservlab:~$ echo hi!
hi!
  네. 되긴 됩니다. 근데 쿼트를 아예 안 쓰고 억지고 해결했다는 문제는 제끼더라도 저게 과연 제대로 작동하고 있는 걸까요? 아래를 봅시다.
mirashi@myservlab:~$ echo hi!yo
-bash: !yo: event not found
  결국 눈가리고 아웅이라는 겁니다. 출력은 됐을지언정 그 출력된 느낌표는 여전히 히스토리 익스팬션 문자로서 역할을 하고 있는 것이죠. 이 문제를 해결하는 방법은 2가지가 있는데 하나는 싱글 쿼트를 사용하는 겁니다. 이 포스트에서 가장 먼저 설명했듯이 싱글 쿼트는 다음 싱글 쿼트가 나오기 전까지의 모든 문자를 문자 그대로 받아들입니다. 조금 전에도 히스토리 익스팬션 문자는 백슬래시(\)와 싱글 쿼트(')로만 문자 자체로서 표시할 수 있다고 했었지요.
mirashi@myservlab:~$ echo 'hi!'
hi!
  다른 방법은 히스토리 익스팬션 문자를 다른 것으로 바꾸거나 아예 없애버리는 겁니다. 히스토리 익스팬션 문자는 'histchars'이라는 이름의 쉘 변수를 통해 지정할 수 있습니다. 참고로 오타 아니고 정말 소문자 맞습니다. 아래처럼 저 변수에 빈 값을 넣으면 자연스럽게 이 기능을 사용하지 않게 되는 것이죠.
mirashi@myservlab:~$ histchars=
mirashi@myservlab:~$ echo "hi!"
hi!
  그런데 배시 쉘은 디폴트로 느낌표를 히스토리 익스팬션 문자로 사용하기 때문에 이 방법은 별로 좋은 방법이라고 할 수 없겠습니다. 자기 호스트에서 저렇게 설정하고 저걸 기준으로 스크립트를 짰다가 다른 호스트에 복사해서 그대로 사용했다간...별로 좋지 못하겠죠. 히스토리 익스팬션은 정말 뻘스러운 기능이 아닌가 싶습니다. 마지막으로 원문의 리플에 달린 뻘스럽지만 그래서 좋은 예제를 보며 포스트를 마치겠습니다.
bash$ cost=2.79
bash$ echo "Alice's sister said, \"Gas costs \$$cost/gal"'!'"\""
Alice's sister said, "Gas costs $2.79/gal!"
bash$ echo 'Bob'"'"'s reply was, "$'"$cost"' is too much!"'
Bob's reply was, "$2.79 is too much!"


2010/10/21 18:00 2010/10/21 18:00