기본 규칙
- 텍스트 쿼팅에는 ` 와 ' 을 사용한다. (" 나 ' 이 아니다)
- ``와 '' 은 ` 와 '으로 간주한다.
- 매크로명은 알파벳, 숫자, 밑줄(_)로 정의된다. 첫 글자는 숫자일 수 없다.
- 줄의 첫 단어가 # 이면 그 줄은 모두 주석으로 간주한다.
- 매크로가 아닌 나머지 문자열은 모두 토큰으로 간주한다.
매크로 실행
매크로명을 써주면 매크로를 실행할 수 있다. 또한 매크로의 인자에 매크로를 포함해 실행할 수 있다.
매크로명
매크로명(인자1, 인자2, 인자3, ...)
아래의 예제를 보자.
define(`f', `1')
f
→1
`f'
→f
1
→1
define(`b', `f')
b
→1
f(define(`f', `2'))
→2
1 - f 라는 매크로를 1로 정의한다. define 에 대해서는 아래에서 설명한다. 매크로를 '정의'한다고만 알아두자.
2 - f 매크로를 실행시키면 3번째 줄과 같이 1이 출력된다. (→ 는 출력을 의미한다고 정의하자)
4 - 1 이라는 매크로는 정의하지 않았기 때문에 입력한 텍스트 그래도 출력된다.
6 - b 를 f 매크로로 정의한다.
7 - b 를 실행하면 f 매크로가 실행되어 결과적으로 8과 같이 1이 출력된다.
9 - f 를 다시 정의했다. 2가 출력된다. (이 부분의 결과는 GNU M4 와 다르다)
windy@wl ~ $ m4
define(`f', `1')
f
1
`f'
f
1
1
define(`b', `f')
b
1
f(define(`f', `2'))
2
Ctrl+D
windy@wl ~ $
매크로 정의
매크로를 정의하기 위해서는, 미리 정의된 매크로인 define 을 사용한다.
define(`매크로명', `매크로정의')
define(`foo', `Hello world.')
foo
→Hello world.
1번째 줄은 foo 라는 매크로를 정의, 2번째 줄은 foo 라는 매크롤를 실행, 3번째 라인은 출력결과다. (→ 는 출력 결과를 의미한다) 아래와 같이 실행해 볼 수 있다.
windy@wl ~ $ vi test
define(`foo', `Hello world.')
foo
windy@wl ~ $ m4 test
Hello world.
아래와 같이 매크로를 매크로로 정의할 수 있다. $1, $2, ..., $9 은 매크로의 인자를 의미한다.
define(`exch', `$2, $1')
define(exch(`expansion text', `macro'))
macro
→expansion text
특별한 인자
$0 ~ $9 뿐만 아니라, 특별한 인자가 아래와 같이 정의되어있다.
$#: 인자의 개수
$*: 인자의 전체
$@: 인자의 전체. 쿼팅됨
아래와 같이 사용할 수 있다.
define(`nargs', `$#')
define(`echo1', `$*')
define(`echo2', `$@')
define(`foo', `This is macro `foo'.')
nargs
→0
nargs()
→1
nargs(arg1, arg2, arg3)
→3
echo1(arg1,arg2,arg3)
→arg1,arg2,arg3
echo2(arg1,arg2,arg3)
→arg1,arg2,arg3
echo1(foo)
→This is macro This is macro foo..
echo2(foo)
→This is macro foo.
매크로의 삭제, 정의내용
undefine(`매크로명')
defn(`매크로명')
undefine은 정의된 매크로를 삭제하고, defn은 매크로에 정의된 정의를 가져온다. 아래와 같은 방법으로 h 매크로를 z 매크로로 변경할 수 있다.
define(`h', `hello')
define(`z', defn(`h'))
undefine(`h')
z
→hello
h
→h
매크로 정의의 임시 저장
pushdef(`매크로명', `정의')
popdef(`매크로명')
pushdef은 매크로의 현재 정의을 저장하고 지정된 정의로 변경하며, popdef은 매크로를 저장된 정의로 변경한다. 임시적으로 정의를 변경할때 유용하게 사용할 수 있다. push, pop 은 내부 구조가 스택구조임을 나타낸다.
define(`foo', `Expansion one.')
pushdef(`foo', `Expansion two.')
foo
→Expansion two.
popdef(`foo')
foo
→Expansion one.
popdef(`foo')
foo
→foo
pushdef 이후 popdef 를 호출하기 전에 define, undefine 를 사용해 저장한 매크로를 다시 정의한 경우, pushdef 로 저장한 정의가 사라진다는 점에 주의하자.
인다이렉트 매크로 호출, 빌트인 매크로 호출
indir, builtin 은 GNU M4 에서만 사용가능하다.
indir(`매크로명', `인자', ...)
builtin(`매크로명', `인자', ...)
매크로를 호출하는 가장 쉬운 방법은 매크로명을 써주면 되지만, indir 매크로를 사용해 비 직접적으로 호출할 수 있다. indir 의 장점은 매크로명에 어떠한 값도 들어갈 수 있다는 점이다. 따라서 대규모 매크로 프로세서에 중복 호출, 매크로 충돌, 의도하지 않은 호출 등을 피하기 위해 사용한다.
define(`$$internal$macro', `Internal macro (name `$0')')
$$internal$macro
→$$internal$macro
indir(`$$internal$macro')
→Internal macro (name $$internal$macro)
builtin 은 빌트인매크로를 실행하기 위해서 사용한다. 보통 빌트인매크로와 동일한 사용자 정의 매크로를 설정할리 없지만, 만약 그렇게 된 경우 빌트인매크로를 명시해 호출할 수 있다.
분기
ifdef(`매크로명', `정의된경우'[, `정의되지않은경우'])
ifelse(문자열1, 문자열2, 같은경우[, 같지않은경우])
ifelse(문자열1, 문자열2, 같은경우, 문자열3, 문자열4, 같은경우[, 같지않은경우])
shift(...)
ifdef 는 매크로가 존재하는지 여부를 알려준다. 존재하면 인자1을 실행한다.
ifdef(`foo', ```foo'' is defined', ```foo'' is not defined')
→`foo' is not defined
define(`foo', `')
ifdef(`foo', ```foo'' is defined', ```foo'' is not defined')
→`foo' is defined
ifelse 는 두 문자열이 동일한지 검사해 같은 경우와 같지 않은 경우를 실행한다.
ifelse(foo, bar, `true')
→
ifelse(foo, foo, `true')
→true
ifelse(foo, bar, `true', `false')
→false
ifelse(foo, foo, `true', `false')
→true
shift 는 주어진 인자를 한번 쉬프트하는 역할을 한다.
shift(foo, bar, baz)
→bar,baz
define(`reverse', `ifelse($#, 0, , $#, 1, $1, `reverse(shift($@)), `$1'')')
reverse(`First', `Second', `Third', `Forth')
→Forth, Third, Second, First
forloop, foreach
아래와 같이 forloop 함수를 만들 수 있다. (forloop 매크로는 GNU M4 아카이브의 examples/forloop.m4 에 들어있다. 좀 더 향상된 버전도 들어있다)
define(`forloop', `pushdef(`$1', `$2')_forloop($@)popdef(`$1')')
define(`_forloop', `$4`'ifelse($1, `$3', `', `define(`$1', incr($1))$0($@)')')
forloop(`i', 1, 8, `i ')
→1 2 3 4 5 6 7 8
forloop(`i', 1, 4, `forloop(`j', 1, 8, `(i, j) ')')
→(1, 1) (1, 2) (1, 3) (1, 4) (1, 5) (1, 6) (1, 7) (1, 8)
→(2, 1) (2, 2) (2, 3) (2, 4) (2, 5) (2, 6) (2, 7) (2, 8)
→(3, 1) (3, 2) (3, 3) (3, 4) (3, 5) (3, 6) (3, 7) (3, 8)
→(4, 1) (4, 2) (4, 3) (4, 4) (4, 5) (4, 6) (4, 7) (4, 8)
아래와 같이 foreach 함수를 만들 수 있다. (foreach 매크로는 GNU M4 아카이브의 examples/foreach.m4, examples/foreachq.m4 에 들어있다. 좀 더 향상된 버전도 들어있다)
define(`foreach', `pushdef(`$1')_foreach($@)popdef(`$1')')
define(`_arg1', `$1')
define(`_foreach', `ifelse(`$2', `()', `', `define(`$1', _arg1$2)$3`'$0(`$1', (shift$2), `$3')')')
foreach(x, (item_1, item_2, item_3), `i - x, ')
→i - item_1, i - item_2, i - item_3,
forloop와 foreachq 는 솔라리스의 M4 에서도 실행된다.
디버깅
dumpdef(...)
traceon(...)
traceoff(...)
debugmode([플래그])
debugfile([파일명])
dumpdef 는 매크로의 정의내용을 stderr으로 출력한다. 인자없이 호출하면, 모든 매크로의 정의를 출력해준다.
define(`foo', `Hello world.')
dumpdef(`foo')
→foo: Hello world.
traceon 과 traceoff 는 지정한 매크로의 정의를 지속적으로 추적하며, 추적 내용을 stderr 으로 출력한다. (출력 내용은 솔라리스 M4 와 GNU M4 가 상이하다)
define(`foo', `Hello world.')
define(`bar', `I am here. foo')
traceon(`foo', `bar')
foo
→Trace(0): foo
→Hello world.
bar
→Trace(0): bar
→Trace(0): foo
→I am here. Hello world.
debugmode, debugfile은 GNU M4에서만 작동한다. 디버그의 출력및 수행 옵션은 GNU m4 실행시 -d 옵션을 주어 변경할 수 있다. 또한 debugmode 빌트인 매크로를 통해 수정할 수도 있으며 debugfile 을 통해 출력을 파일로 지정할 수 있다.
define(`foo', `Hello~')
traceon(`foo')
debugmode()
foo
→m4trace: -1- foo -> `Hello~'
→Hello~
debugmode
foo
→m4trace: -1- foo
→Hello~
debugmode(`+l')
foo
→m4trace:8: -1- foo
→Hello~
debugmode 에 사용할 수 있는 플래그는 GNU M4 1.4.16 macro processor - 7.3 Controlling debugging output을 참고하자.
-
dnl
changequote(인용시작부호, 인용마침부호)
changecom(주석시작부호, 주석마침부호)
changeword(`정규표현식')
m4wrap
dnl (Discard to Next Line) 은 입력시 다음 줄 까지 나오는 입력을 무시한다. 따라서 새 라인을 제거하는 역할을 하기도 한다. (새라인 캐릭터 뿐만 아니라, dnl 뒤에 나오는 모든 문자를 무시한다)
windy@wl ~ $ vi test
define(`foo', `Hello World')
foo
windy@wl ~ $ m4 test
Hello World
windy@wl ~ $ vi test
define(`foo', `Hello World') dnl
foo
windy@wl ~ $ m4 test
Hello World
changequote 와 changecom 는 각각 쿼트 부호와 주석 부호를 변경하는 역할을 한다. changecom 이 인자 없이 실행되는 경우 어떠한 것도 주석으로 간주하지 않는다.
changequote([[,]])
define([[foo]], [[Macro [[[foo]]].]])
foo
→Macro [foo].
define(`comment', `COMMENT')
# This is a comment
→# This is a comment
/* This is not a comment */
→/* This is not a COMMENT */
changecom(`/*', `*/')
# Not a comment anymore
→Not a COMMENT anymore
/* This is a comment */
→This is a comment
changeword 는 M4에서 단어의 정의를 변경할 수 있도록 해준다. 이 문서 윗 부분에 '매크로명'은 알파벳, 숫자, 밑줄로 구성되고 첫 글자는 숫자일 수 없다고 정의했는데, 이를 바꿀 수 있다는 의미다. GNU M4에서만 실행되며 실험적인 빌트인이다. configure 씨 --enable-changeword 옵션을 줘야 한다.
changeword(`[_a-zA-Z0-9]+')
define(1, 0)
1
→0
m4wrap은 텍스트를 임시 보관소에 저장한다. 입력된 텍스트를 저장하려면 아래와 같이 한다. m4wrap 은 LIFO(Last In, First Out) 형식으로 작동한다. FIFO가 필요하다면 examples/wrapfifo.m4 파일을 참고해 작성하자.
windy@wl ~ $ vi m4wrap.m4
define(`cleanup', `클린업
')
m4wrap(`cleanup')
이문장은 첫번째줄에 출력됨.
windy@wl ~ $ m4 m4wrap.m4
이문장은 첫번째줄에 출력됨.
클린업
파일 삽입
include(`파일명')
sinclude(`파일명')
지정한 파일을 삽입한다. include 는 파일을 삽입할 수 없으면 오류를 발생하지만, sinclude 는 오류가 발생하지 않는다.
windy@wl ~ $ cat incl.m4
This is incl.m4
windy@wl ~ $ /usr/ccs/bin/m4
include(`incl.m4')
This is incl.m4
include(`none')
/usr/ccs/bin/m4:-:2 파일을 열수 없음
include(none)
1 windy@wl ~ $ /usr/ccs/bin/m4
sinclude(`incl.m4')
This is incl.m4
sinclude(`none')
Ctrl+D
windy@wl ~ $
출력
divert([번호])
undivert
divnum
divert 는 앞으로의 출력을 지정한 번호를 가진 버퍼에, divert 를 인자 없이 호출할때까지 저장한다. 또한 입력이 종료되는 때에 저장했던 내용을 한번에 출력한다.
divert(1)dnl
This text is diverted.
divert
This is not diverted.
→This is not diverted.
→This text is diverted.
보통 아래와 dnl을 사용하지 않고 새 줄을 출력하지 않기 위해 사용하곤 한다.
divert(-1)
define(`foo', `Macro `foo'.')
define(`bar', `Macro `bar'.')
divert
undivert 는 divert 된 내용을 바로 출력하기 위해 사용한다.
divert(1)dnl
This text is diverted.
divert
undivert(1)
→This text is diverted.
This is not diverted.
→This is not diverted.
divnum은 divert된 번호를 보여준다. 아래와 같은 응용이 가능하다.
define(`cleardivert', `pushdef(`_n', divnum)divert(`-1')undivert($@)divert(_n)popdef(`_n')')
문자열
len(`문자열')
index(`문자열', `포함된문자열')
substr(`문자열', 시작위치[, 길이])
regexp(`문자열', `정규표현식'[, `대체문자열'])
translit(`문자열', `문자들'[, `대체문자들'])
patsubst(`문자열', `정규표현식'[, `대체문자열'])
format(`포맷문자열', ...)
len 은 문자열의 길이를, index 는 문자열안에 특정 문자열의 위치를, substr 은 문자열을 자른다.
len()
→0
len(`WindyHana Solanara')
→18
index(`WindyHana Solanara', `Sol')
→10
index(`WindyHana Solanara', `zzz')
→-1
substr(`WindyHana Solanara', 5)
→Hana Solanara
substr(`WindyHana Solanara', 5, 8)
→Hana Sol
regexp는 정규표현식을 사용해 문자열을 검색하거나 치환한다. GNU M4 에서만 사용가능하다. 문자열을 찾지 못한 경우 -1을 출력한다.
regexp(`GNUs not Unix', `\<[a-z]\w+')
→5
regexp(`GNUs not Unix', `\<Q\w*')
→-1
regexp(`GNUs not Unix', `\w\(\w+\)$', `*** \& *** \1 ***')
→*** Unix *** nix ***
translit 은 문자열의 문자를 변환한다. GNU M4의 경우 유닉스 버전보다 확장되어있다.
※ GNU M4
translit(`abc')
→m4:stdin:1: Warning: too few arguments to builtin `translit'
→abc
translit(`GNUs not Unix', `U')
→GNs not nix
translit(`GNUs not Unix', `U', `Z')
→GNZs not Znix
translit(`GNUs not Unix', `A-Z')
→s not nix
translit(`GNUs not Unix', `a-z', `A-Z')
→GNUS NOT UNIX
translit(`GNUs not Unix', `A-Z', `z-a')
→tmfs not fnix
translit(`GNUs not Unix', `GNU', `UNG')
→UNGs not Gnix
※ M4
translit(`abc')
→abc
translit(`GNUs not Unix', `U')
→GNs not nix
translit(`GNUs not Unix', `U', `Z')
→GNZs not Znix
translit(`GNUs not Unix', `A-Z')
→GNUs not Unix
translit(`GNUs not Unix', `a-z', `A-Z')
→GNUs not Unix
translit(`GNUs not Unix', `GNU', `UNG')
→UNGs not Gnix
patsubst 는 포괄적으로 문자열을 치환한다. (regexp 는 정규표현식을, patsubst 는 GNU EMACS 정규표현식을 사용한다) patsubst 는 GNU M4 에서만 사용가능하다. GNU M4 1.4.16 macro processor - 11.6 Substituting text by regular expression을 읽어보자.
patsubst(`GNUs not Unix', `\<', `OBS: ')
→OBS: GNUs OBS: not OBS: Unix
patsubst(`GNUs not Unix', `[A-Z][a-z]+')
→GN not
아래에 조금 더 복잡한 예제가 있다. GNU M4의 배포판의 examples/capitalize.m4 을 참고하자.
define(`upcase', `translit(`$*', `a-z', `A-Z')')dnl
define(`downcase', `translit(`$*', `A-Z', `a-z')')dnl
define(`capitalize1', `regexp(`$1', `^\(\w\)\(\w*\)', `upcase(`\1')`'downcase(`\2')')')dnl
define(`capitalize', `patsubst(`$1', `\w+', `capitalize1(`\0')')')dnl
format 은 문자열을 형식에 맞춰 출력한다. GNU M4에서 지원한다. 지원되는 형식은 printf(3C)을 참고하자.
define(`foo', `The brown fox jumped over the lazy dog')dnl
format(`The string "%s" is %d characters long', foo, len(foo))
→The string "The brown fox jumped over the lazy dog" is 38 characters long
수치
incr(숫자)
decr(숫자)
eval(수식[, 진법[, 너비]])
incr은 숫자를 하나 증가시키고, decr 은 하나 뺀다. eval 은 지정한 수식을 계산한다.
incr(4)
→5
decr(7)
→6
eval(-3 * 5)
→-15
define(`foo', `666')
eval(foo/6)
→111
eval(`foo'/6) 오류!
eval 에서 사용할 수 있는 수식은 아래와 같다.
- (, ): 괄호
- +, -, ~, !: 양수, 음수, 비트 not, 논리 not
- **: 지수승
- *, /, %: 곱하기, 나누기, 나머지
- +, -: 더하기, 빼기
- <<, >>: 쉬프트
- >, >=, <, <=: 비교 연산
- ==, !=: 등가 비교 연산
- &: 비트 and
- ^: 비트 xor
- |: 비트 or
- &&: 논리 and
- ||: 논리 or
시스템
__gnu__
__os2__ os2
__unix__ unix
__windows__ windows
syscmd(셸명령)
esyscmd(셸명령)
sysval
maketemp(`템플릿')
__gnu__, __os2__, __unix__, __windows__ 은 GNU M4 에서만 정의되어있는 플랫폼 식별자 매크로다. 만약 GNU M4 에서 -G 옵션을 주면 os2, unix, windows 으로 정의한다.
※ GNU M4 (솔라리스10에서 실행)
ifdef(`unix', `active', `inactive')
→inactive
ifdef(`__unix__', `active', `inactive')
→active
ifdef(`__windows__', `active', `inactive')
→inactive
ifdef(`__gnu__', `active', `inactive')
→active
※ GNU M4 (솔라리스10에서 -G옵션과 같이 실행)
ifdef(`unix', `active', `inactive')
→active
ifdef(`__unix__', `active', `inactive')
→inactive
ifdef(`__gnu__', `active', `inactive')
→inactive
※ M4 (솔라리스10)
ifdef(`unix', `active', `inactive')
→active
ifdef(`__unix__', `active', `inactive')
→inactive
ifdef(`__gnu__', `active', `inactive')
→inactive
syscmd 는 지정한 셸커맨드를 실행하고, esyscmd 는 셸커맨드를 실행한 후 stdout 으로 출력된 문자열을 돌려준다. sysval 은 마지막에 실행된 커맨드의 종료코드를 알려준다. maketemp 는 지정한 형식으로 임시 파일을 생성한다. esyscmd는 GNU M4 에서만 작동한다.
define(`passwd', `esyscmd(echo abc)')
passwd
→abc
syscmd(`true')
sysval
→0
maketemp(`/tmp/fooXXXXXX')
→/tmp/foo31aOrf
기타
errprint(`메시지')
__program__
__file__
__line__
m4exit([코드])
errpint 는 지정한 메시지를 stderr 으로 출력한다. __program__, __file__, __line__ 은 실행파일명(셸 스크립트의 $0에 해당), 파일명과 줄 번호다. m4exit 는 m4 를 바로 종료한다.
windy@wl ~ $ vi fatal
define(`fatal_error', `errprint(`m4: '__program__:__file__:__line__`: fatal error: $* ')m4exit(1)')
fatal_error(`This is a BAD one, buster')
windy@wl ~ $ /usr/ccs/bin/m4 fatal
m4: __program__:__file__:__line__: fatal error: This is a BAD one, buster
windy@wl ~ $ /usr/local/bin/m4 fatal
m4: /usr/local/bin/m4:fatal:2: fatal error: This is a BAD one, buster