내가 리눅스로 뭘 하고 있는지 로그 남기기 (파이썬)

지난번 포스팅에서 맥에서 내가 컴퓨터로 무엇을 하고 있는지 기록하는 스크립트를 어떻게 만들었는지에 대해 이야기했었다.

요번엔 리눅스다. 지난번 스크립트는 애플스크립트로 작성되어 맥에서만 동작했지만, 이번엔 리눅스에서 동작할 수 있도록 파이썬으로 작성할 것이다.

이 스크립트의 전체 소스코드는 여기서 받을 수 있다.

만들기

일단 현재 사용중인 윈도를 얻어야 한다. 나는 wnck 모듈을 사용했다. 그냥 쓰기 쉬워보여서.

import wnck

def get_current_window():
    try:
        return wnck.screen_get_default().get_active_window()
    except AttributeError:
        traceback.print_exc(file=sys.stderr)
        return False

이렇게 해서 얻은 윈도에서 프로세스 id를 얻은 다음, 다시 그 프로세스 id에서 프로세스 이름을 얻는다. 윈도 이름은 그냥 get_name() 메소드로 얻을 수 있다.

def log(output):
    // ...
    window = get_current_window()
    name = get_name(window.get_pid())
    window_name = window.get_name()
    // ...

def get_name(pid):
    return [ps.name for ps in psutil.get_process_list() if ps.pid == pid][0]

ip, ssid, 날짜도 얻는다. ip하고 ssid는 그냥 ip와 iwconfig를 직접 실행해서 얻었다.

def get_ip():
    #Use ip route list
    try:
        arg = 'ip route list'
        p = subprocess.Popen(arg, shell=True, stdout=subprocess.PIPE)
        data = p.communicate()
        sdata = data[0].split()
        ipaddr = sdata[ sdata.index('src')+1 ]
        return ipaddr
    except Exception, e:
        traceback.print_exc(file=sys.stderr)
        return '-'

def get_ssid():
    arg='iwconfig'
    p = subprocess.Popen(arg, shell=True, stdout=subprocess.PIPE, stderr=None)
    for line in p.communicate():
        if line:
            match = re.search('ESSID:"(.*)"', line)
            if match:
                return match.groups()[0]

    return '-'

def get_date():
    return time.strftime('%Y/%m/%d-%H:%M', time.localtime())

실제로 파일에 기록하는 역할을 하는 log() 함수도 만든다.

def log(output):
    try:
        window = get_current_window()
        if not window:
            sys.stderr.write('failed to get windown')
            return True

        format = 'a4'
        output.write("%s %s %s %s %s %sn" % (
            format, get_ip(), get_ssid(), get_date(),
            get_name(window.get_pid()), window.get_name()))
    except Exception, e:
        traceback.print_exc(file=sys.stderr)

    return True

마지막으로 60초에 한번씩 실행되도록 glib.timeout_add 로 설정해주고, gtk.main()을 호출하면 된다.

glib.timeout_add(60 * 1000, log, output) # 60sec
gtk.main()

문제와 해결방법

osx때는 없었던 문제가 두 가지 정도 있어서 애를 좀 먹었다.

gtk를 사용할 수 없는 환경에서는 실행 불가

이 때문에 osx때 했던 것 처럼 crontab에 넣는 방법은 사용할 수 없다. 그래서 대신 우분투 Startup Applications에 등록했다.

ssid를 알아내려면 루트 권한이 필요

리눅스에서는 iwconfig로 ssid를 알아내려면 루트 권한이 필요하다.

이 문제의 해결방법엔 두 가지가 있다.

단순한 방법: iwconfig가 파일 owner(즉 root)의 권한을 갖고 실행되도록 만들기

sudo chmod +s /sbin/iwconfig

조금 더 정교한 방법: iwconfig가 ssid를 알아낼 수 있도록 권한주기

sudo setcap cap_net_raw,cap_net_admin=eip /sbin/iwconfig

나는 후자를 택했다. 누가 실행하든 root 권한으로 실행된다는 건 좀 겁나니까.

이 해결방법에 더 자세히 알고 싶다면 내가 스택오버플로우에 올린 질문을 보라. TOC라는 분이 매우 친절하게 답변을 달아주었다.

이렇게 해서 나는 osx나 리눅스를 쓸 때 내가 컴퓨터로 무엇을 했는지에 대한 기록을 항상 남기고 있다. 나는 깨어있는 시간의 절반을 컴퓨터 앞에서 보내고 있으니 (( 이 사실 자체도 이 로그를 통해서 안 것이다. 내가 컴퓨터를 사용하는 시간은 하루 평균 8시간을 약간 넘는 정도다. )), 이는 결국 내 인생의 절반에 대한 로그를 남기고 있다는 것과 같다.

현재는 주로 ‘내가 그때 뭘 했더라’나 ‘그걸 하는데 시간을 얼마나 썼더라’를 확인해야 할 때 이용하고 있으며, 가끔씩 내가 컴퓨터로 뭘 주로 하는지 알아보고 싶을때도 사용한다.

추가

로그가 실제로 어떤식으로 남는지 궁금해하실 분들을 위해, 내가 이 글을 작성하는 동안 남은 로그를 올려본다.

a4 192.168.0.22 nori 2012/09/09-01:40 gnome-terminal worklog-py.md + (~/blog) - VIM
a4 192.168.0.22 nori 2012/09/09-01:41 gnome-terminal worklog-py.md + (~/blog) - VIM
a4 192.168.0.22 nori 2012/09/09-01:42 gnome-terminal worklog.py (~/mysrc/worklog) - VIM
a4 192.168.0.22 nori 2012/09/09-01:43 chromium-browser Posts ‹ npcode — WordPress - Chromium
a4 192.168.0.22 nori 2012/09/09-01:44 gnome-terminal worklog-py.md (~/blog) - VIM
a4 192.168.0.22 nori 2012/09/09-01:45 gnome-terminal worklog.py + (~/mysrc/worklog) - VIM
a4 192.168.0.22 nori 2012/09/09-01:46 gnome-terminal worklog.py (~/mysrc/worklog) - VIM
a4 192.168.0.22 nori 2012/09/09-01:47 chromium-browser wnck python - Google 검색 - Chromium
a4 192.168.0.22 nori 2012/09/09-01:48 gnome-terminal worklog-py.md + (~/blog) - VIM
a4 192.168.0.22 nori 2012/09/09-01:49 gnome-terminal worklog-py.md + (~/blog) - VIM
a4 192.168.0.22 nori 2012/09/09-01:50 gnome-terminal worklog-py.md (~/blog) - VIM
a4 192.168.0.22 nori 2012/09/09-01:51 gnome-terminal worklog-py.md + (~/blog) - VIM
a4 192.168.0.22 nori 2012/09/09-01:52 gnome-terminal worklog-py.md + (~/blog) - VIM
a4 192.168.0.22 nori 2012/09/09-01:53 gnome-terminal worklog-py.md + (~/blog) - VIM
a4 192.168.0.22 nori 2012/09/09-01:54 gnome-terminal worklog-py.md + (~/blog) - VIM
a4 192.168.0.22 nori 2012/09/09-01:55 gnome-terminal worklog-py.md + (~/blog) - VIM
a4 192.168.0.22 nori 2012/09/09-01:56 gnome-session-properties Edit Startup Program
a4 192.168.0.22 nori 2012/09/09-01:57 gnome-terminal worklog-py.md + (~/blog) - VIM
a4 192.168.0.22 nori 2012/09/09-01:58 gnome-terminal worklog-py.md + (~/blog) - VIM
a4 192.168.0.22 nori 2012/09/09-01:59 gnome-terminal iwconfig (~/wiki) - VIM
a4 192.168.0.22 nori 2012/09/09-02:00 gnome-terminal worklog-py.md + (~/blog) - VIM
a4 192.168.0.22 nori 2012/09/09-02:01 gnome-terminal worklog-py.md + (~/blog) - VIM
a4 192.168.0.22 nori 2012/09/09-02:02 gnome-terminal worklog-py.md (~/blog) - VIM
a4 192.168.0.22 nori 2012/09/09-02:03 gnome-terminal worklog-py.md + (~/blog) - VIM
a4 192.168.0.22 nori 2012/09/09-02:04 gnome-terminal worklog-py.md (~/blog) - VIM
a4 192.168.0.22 nori 2012/09/09-02:05 gnome-terminal worklog-py.md (~/blog) - VIM
a4 192.168.0.22 nori 2012/09/09-02:06 gnome-terminal linux (~/wiki) - VIM
a4 192.168.0.22 nori 2012/09/09-02:07 gnome-terminal worklog-py.md (~/blog) - VIM
a4 192.168.0.22 nori 2012/09/09-02:08 gnome-terminal COMMIT_EDITMSG + (~/mysrc/worklog/.git) - VIM
a4 192.168.0.22 nori 2012/09/09-02:09 gnome-terminal worklog-py.md + (~/blog) - VIM
a4 192.168.0.22 nori 2012/09/09-02:10 gnome-terminal worklog-py.md + (~/blog) - VIM
a4 192.168.0.22 nori 2012/09/09-02:11 gnome-terminal worklog-py.md + (~/blog) - VIM
a4 192.168.0.22 nori 2012/09/09-02:12 gnome-terminal worklog-py.md + (~/blog) - VIM
a4 192.168.0.22 nori 2012/09/09-02:13 gnome-terminal worklog-py.md (~/blog) - VIM
a4 192.168.0.22 nori 2012/09/09-02:14 gnome-terminal worklog-py.md (~/blog) - VIM
a4 192.168.0.22 nori 2012/09/09-02:15 chromium-browser npcode - Chromium
a4 192.168.0.22 nori 2012/09/09-02:16 gnome-terminal  ~/blog
a4 192.168.0.22 nori 2012/09/09-02:17 chromium-browser 새로운 글 추가 ‹ npcode — WordPress - Chromium
a4 192.168.0.22 nori 2012/09/09-02:18 chromium-browser npcode » 내가 리눅스로 뭘 하고 있는지 로그 남기기 (파이썬) - Chromium
a4 192.168.0.22 nori 2012/09/09-02:19 chromium-browser 글 편집 ‹ npcode — WordPress - Chromium
a4 192.168.0.22 nori 2012/09/09-02:20 chromium-browser 글 편집 ‹ npcode — WordPress - Chromium
Advertisements

내가 맥으로 뭘 하고 있는지 로그 남기기 (애플스크립트)

예전부터 내가 컴퓨터로 뭘 하고 있는지 기록하는 스크립트를 꼭 만들고 싶었는데 어째 쉽지가 않아 차일피일 미루다가, 작년에 OSX를 처음 쓰면서 애플스크립트로 하나 만들었다.

애플스크립트는 이걸 만들면서 처음 써 봤는데, 전혀 적응이 되지 않아서 결국 이게 처음이자 마지막이 되었다.

이 스크립트의 전체 소스코드는 여기서 받을 수 있다.

만들기

내가 원하는 것은 매우 간단하다. 매 분 내가 사용하고 있는 애플리케이션에 대한 정보를 로그파일에 기록하는 것이다. 이 때 ip와 ssid(무선인터넷 ap 이름)도 같이 기록한다. 내가 어디서 컴퓨터를 사용했는지 추측할 수 있게 해주는 단서가 되게 때문이다.

이 요구를 해결하기 위한 나의 전략 역시 단순하다. 현재 사용중인 애플리케이션에 대한 정보를 한줄로 표준출력하는 스크립트를 작성하고, 매 분 실행되도록 cronjob으로 등록하는 것이다.

현재 쓰고있는 애플리케이션은 frontmost가 true일 것이다. 그 애플리케이션의 이름을 얻어 frontApp으로 저장한다.

tell application "System Events"
    set frontApp to name of first application process whose frontmost is true
end tell

윈도 타이틀도 유용한 정보다. 웹브라우저라면 거기에 웹사이트에 타이틀이 뜨니 특히 좋다. 아까 얻은 애플리케이션의 front window를 얻고 그 윈도의 타이틀을 가져와서 window_title로 저장한다.

tell application frontApp
    if the (count of windows) is not 0 then
        set window_title to name of front window
    end if
end tell

날짜, ip, ssid는 다음의 방법으로 얻었다. 애플스크립트에서 다시 쉘명령을 실행하다니 조금 지저분하다.

set theDate to (do shell script "date '+%Y/%m/%d-%H:%M'")
set theIp to do shell script "/sbin/ifconfig en0 | grep 'inet ' | awk '{print $2}'"
set theSsid to do shell script "/System/Library/PrivateFrameworks/Apple80211.framework/Versions/A/Resources/airport -I | grep -E '\<SSID\>' | awk '{print $2}'"

표준출력은 쉽다. 그냥 변수 이름을 그대로 쓰면 된다. 문자열끼리 연결할때는 &를 쓴다.

"a4 " & theIp & " " & theSsid & " " & theDate & " " & frontApp & ": " & window_title

이렇게 스크립트를 만든 뒤, 1분에 한번씩 실행하여 표준출력된 결과를 파일에 추가하도록 하면 완성이다. crontab -e 해서 크론탭 편집에 들어가 다음과 같은 식으로 기록되도록 한 줄을 추가한다. (에러메시지도 기록이 남게 하고 싶으면 뒤에 2>> ~/work.error.log 식으로 덧붙여준다)

* * * * * osascript ~/bin/worklog.osa >> ~/work.log

ps. 이름을 worklog라고 붙여놓았는데 이거 아무리 봐도 worklog하곤 다르다. 좋은 이름이 없을까?

다음편 예고

안타깝게도 애플스크립트니까 OSX에서만 돈다. 다음번엔 같은 일을 하는 리눅스용 파이썬 스크립트에 대해 포스팅해보겠다. 리눅스용은 몇가지 사정으로 인해 좀 더 복잡하다.

가장 만족하는 IDE

내가 여태껏 써 본 IDE 중 가장 만족하는 것은 Visual Basic이다.

Visual Basic 5.0 Professional Edition 아카데믹

아직도 내 방의 서랍장 위엔 Visual Basic 5.0 Professional Edition 아카데믹이 놓여 있다. 고등학생이었던 98년 2월에 구입한 것인데, 당시 가격 26만원으로 지금껏 돈 주고(내 돈은 아니었지만) 산 소프트웨어 중에서 제일 비싸다. 아마 회사 돈으로 산 것 까지 포함해도 그럴 것이다.

97년에 학교에서 4.0을 써보면서 비주얼 베이직을 처음 접했는데, 콘트롤을 마우스로 슥슥 그려서 붙이고 더블클릭해서 이벤트를 추가하는 것 만으로 윈도우 애플리케이션이 간단히 만들어지는 RAD는 당시의 나에게는 마치 마술이나 기적과 같은 것이었다. 그래서 5.0 베타버전이었나 하여튼 시험용 버전을 구해서 쓰다가 98년에 컴퓨터를 바꾸면서 같이 샀다.

비주얼 베이직은 IDE로서의 기능도 충실해서, 디버깅도 더할나위없이 굉장히 잘되고, 문법오류는 한 줄 입력할 때마다 바로 잡아내며, 굉장히 친절하고 자세한 도움말을 무려 “한글로” 제공했고, 심지어 대소문자까지 맞춰주는 포매팅 기능도 있었던 것으로 기억한다. 이런 기능들을 다 갖추고도 느리지도 않았다(비주얼 베이직 애플리케이션 말고 IDE 자체가). 지금 쓰고 있는 이클립스하고 비교해도 전혀 손색이 없다…가 아니라 안정성 면에서는 비주얼 베이직이 훨씬 낫지 않나 싶다.

원래 윈도우에선 GUI 프로그래밍이 다 이런줄 알았다가 이 때로부터 3년후 Visual C++ 6.0으로 MFC 프로그래밍을 해보고 좌절하게 된다. C++로 윈도우 프로그래밍은 어려운거구나 하고 살다가, 다시 1년 후 볼랜드 C++을 만나고 나서는 꼭 그런것만은 아니라는 걸 알고 결국 MS 안티가 되었다.

node.js zlib 스트림의 문제

얼마전에, 회사일로 Git의 pack에서 object를 가져오는 기능을 node.js 기반으로 구현하다가 문제를 만났다.

pack은 대략 아래와 같은 포맷으로 구성되어있다. (엉성해서 죄송)

...hhzzzzzzzzzzzzhhzzzzzzzzzzzhhzzzzzzzzz...

(h는 헤더, z는 deflate로 압축된 영역)

pack파일은 git의 object들을 합쳐놓은 파일이므로, 크기가 굉장히 커질 수 있다. 저장소의 모든 object가 pack 파일 하나에 다 들어가는 경우도 얼마든지 있을 수 있다. 따라서 object하나만 얻으면 되는 상황에서 파일 전체를 읽는 것은 비효율적이므로, 원하는 부분만 정확히 읽어들일 필요가 있다.

근데 그게 참 쉽지 않은데, pack에서 object하나를 얻을 때, 어디서부터 읽어야 하는지는 쉽게 알 방법이 존재하지만(인덱스 파일이 있다) 어디까지 읽어야 하는지는 읽어보지 않고서는 모르기 때문이다. 파일을 읽고 압축을 풀어봐야 알 수 있다.

따라서 나는 이 문제를, ReadStream에서 파일을 읽고 InflateStream에게 넘겨준 뒤, 압축해제가 완료되면 파일 읽기를 중단하도록 하면 해결될 것으로 생각했다. 코드는 아래와 같다.

in = require('fs').createReadStream('pack-031a12679260de44c3e123e42d17e5f7803ab016.pack')
inflate = require('zlib').createInflate();
out = process.stdout;

in.pipe(inflate).pipe(out)

그러나 이 코드는 두 가지 이유때문에 의도대로 동작하지 않는다:

  1. InflateStream에서 압축해제가 완료되어도 아무런 이벤트가 발생하지 않는다. 그래서 끝났는지 알 길이 없다.
  2. 압축해제가 완료되었을 때 이벤트가 발생한다 해도, ReadStream에게 더 이상 파일을 읽을 필요가 없음을 알려줄 방법이 없다.

일단 첫번째 문제는 node.js의 zlib 구현에 문제가 있는 것이라 생각했다. 그래서 압축해제가 완료되면(Z_STREAM_END가 리턴되면) end 이벤트(아이작이 close가 아니라 end가 맞다고 조언해주었다)가 발생하도록 고치고 pull request를 보냈다.

두번째 문제는 깔끔한 해결책을 찾지 못했다. 그냥 inflate에서 end 이벤트가 발생하면 input.destory()를 호출해서 강제로 파일 읽기를 중단시켰다.

inflate.on('end', function() {
  input.destroy();
});

이 문제에 대해서는 Nodeconf 2012에서 Stream에 대한 주제로 발표를 했던 Yammer의 Marco Rogers에게도 도움을 요청한 상태이다. 뭔가 깔끔한 해결책이 나와주면 좋겠지만 그렇지 못한다면 그냥 이대로 해야할지도 모르겠다.

PHP, Perl, Ruby, Python 비교

나는 왜 PHP보다 Python을 좋아하는가? 깔끔한 문법이 일단 매력이지만 그게 다가 아니다.

기능 비교 (( http://en.wikipedia.org/wiki/First-class_function, http://en.wikipedia.org/wiki/List_comprehension, http://en.wikipedia.org/wiki/Mixin ))

일단은 언어의 강력함에서 큰 차이를 보인다. 생산성에 신경쓰는 언어들이라면 당연히 지원해 줄 법한 기능들에 대해 PHP는 너무나도 무심하다.

PHP Perl Ruby Python
Higher-order functions Arguments Yes Yes Yes Yes
Results Yes Yes Yes Yes
Non-local variables Nested functions No (( Nested functions are created at global scope once the outer function is executed. )) 6 Yes Yes
Anonymous functions 5.3 Yes Yes Yes
Closures 5.3 (( Anonymous functions could be created using create_function but could not refer to variable in the outer scope. )) Yes Yes Yes
Partial application No 6 1.9 (( Explicit currying with Proc#curry )) 2.5 (( Explicit partial application with functools.partial ))
List comprehension No No No Yes
Mixin No 6 Yes Yes

Anonymous functions와 Closures를 PHP 5.3에서 지원하기는 하지만 그 기능을 웹애플리케이션을 개발할 때 사용하기엔 PHP 5.3이 너무 최근(2009년)에 나왔다. 내 애플리케이션의 요구사항을 PHP 5.3 이상으로 하는 것은 시기상조다.

성능 비교

아래의 링크를 보면 모든 테스트케이스에서 python이 php보다 훨씬 좋은 성능을 보인다. pypy나 psyco를 사용한다면 상대가 안되는 수준이 될 듯.

리눅스의 서브디렉토리 갯수 한계 (ext 계열)

리눅스에서 ext 계열 파일시스템의 서브디렉토리 생성 갯수 한계는 아래와 같다.

ext2, ext3

ext2, ext3의 제한에 대한 정의는 리눅스 커널 코드의 EXT*_LINK_MAX 값에 정의되어 있다. 아래는 linux-2.6.32.8에서 grep으로 찾아본 것이다.

include/linux/ext2_fs.h:#define EXT2_LINK_MAX		32000
include/linux/ext3_fs.h:#define EXT3_LINK_MAX		32000

이처럼 32000으로 정의되어있다. 그런데 모든 디렉토리는 기본적으로 ‘.’ 과 ‘..’ 의 링크를 이미 갖고 있기 때문에 결국 추가로 만들 수 있는 서브디렉토리의 총 갯수는 31998개가 된다. mkdir 유틸리티로 이 갯수를 초과하여 디렉토리를 생성하려고 하면 Too many links 에러를 만나게 될 것이다. ((실험해보지는 못했다. 난 ext4를 쓰는 관계로))

위 상수를 이용한 에러처리 코드의 예는 다음과 같다. (fs/ext3/namei.c의 ext3_mkdir 함수)

        if (dir->i_nlink >= EXT3_LINK_MAX)
                return -EMLINK;

ext4

ext4도 비슷한 값이 정의되어 있긴 한데, ((wikipedia 등에서는 64000으로 되어있다. 뭐가 맞는지는 모름))

fs/ext4/ext4.h:#define EXT4_LINK_MAX		65000

이 제한을 넘더라도 서브디렉토리를 계속해서 만들 수 있다. 즉, 제한이 없다.

PHP의 당황스런 특징들

PHP는 나름 태생이 특이한 언어다. 현재 웹에서 널리 쓰이고 있는 많은 서버사이드 언어들(Java, PHP, Python, Ruby 등등 ((등등 취급해서 미안하다. perl 그리고 c)) ) – 중에서 거의 유일하게 ‘진짜로 웹개발에 특화된’ 언어다.

그래서 그런건지 PHP는 다른 언어를 쓰다 온 사람들을 희한한 방식으로 깜짝 놀래키곤 한다. 지난 3년간 나를 당황시킨 PHP의 독특한 동작 몇가지를 소개해 보겠다.

0 == ‘string’

<?php
print 0=='hello';

위 코드를 실행하면 놀랍게도 1이 출력된다.

PHP는 어째서 숫자 0과 ‘hello’을 같다(equal)고 판단한 것인가? 그것은 숫자와 문자열을 equal operator로 비교를 시도하는 경우, PHP는 문자열을 숫자로 변환하기 때문이다. ‘hello’를 숫자로 변환하면 0이 되므로 PHP는 0과 ‘hello’를 같다고 판단한다.

PHP 개발자들은 ’42’ == 42 와 같은 평가식이 아무 에러 없이 참을 반환하는 편리함을 누리는 동시에, 위와 같은 PHP 고유의 독특한 해석도 받아들여야만 한다.

이렇게 동작하게 된 배경에는 PHP가 태생적으로 HTML에 embeded해서 사용하기 위한 언어라는 사실이 자리하고 있을 것이다. 그런 형태로 사용하려면 가급적 코드가 짧은 것이 바람직할 것이며, 잦을 수 밖에 없는 문자열 변환 함수 호출도 생략할 수 있게 하고 싶었던 것이 아닐런지.
이런 특성은 PHP가 perl의 weak typing을 그대로 물려받은 것이 아닐까 싶다.

함수 이름에 대소문자를 구분하지 않는다.

<?php
function foo() {
    print 'hello';
}
FoO();

C 개발자가 보면 기절초풍할 코드지만 문제없이 잘 동작한다. 더 신기하게도, 함수 이름에선 대소문자를 전혀 구분하지 않지만 변수 이름에선 정확하게 구분한다.

이것 역시 PHP가 웹개발용 언어이기 때문일 것이다. 내 추측으로는 과거 HTML 태그를 소문자로 써야 하는건지 대문자로 써야하는 건지 명확하지 않던 시절의 잔재가 아닐까 싶다. HTML element를 만들어주는 함수를 PHP로 작성했을 때(예: a(), table(), …) 대문자로 호출하든 소문자로 호출하든 잘 동작하게 하고 싶었던 게 아닐까?

foo()[0] 불가능

<?php
function foo() {
    return array('first', 'second');
}

$result = foo();
print $result[0]; # print 'first'
print foo()[0]; # ?

두 print 문 모두 문제없이 ‘first’ 가 출력될 것으로 기대하게 되지만,  사실 두번째 print 문은 parse 에러를 일으킨다.

PHP Parse error:  syntax error, unexpected '[' in - on line 8

함수 호출문 뒤에 첨자 연산자를 붙여 사용할 수 없다. 이유는 나도 모르겠다. 파서를 단순하게 유지하기 위함일 것이라고 짐작할 뿐이다.

PHP 6에서도 안될 예정이다: http://bugs.php.net/bug.php?id=43577