# 수정본.
2018.11.17 에 쓴 초판을 수정했다. xargs 에 대해 확실하게 정리를 하고자 하긴 했는데, 아마도 이 글 내용이 맞으리라 생각한다.
습관으로, 그저 손가락 근육이 기억하는대로 파이프(라인)을 써왔다. Redirection 도 마찬가지.
오늘, 쓰잘 데 없어보이나, 결코 쓰잘 데 없지는 않은 뭔가를 하다가 파이프의 벽에 또 부딪혔다.
자, 아래 명령이 왜 내가 원하는대로 되지 않을까?
$ find . -type f | cat ./deleteme.txt ./config.cson ./github.cson ./init.coffee ./keymap.cson ./nohup.out ./package-deps-state.json
내가 원했던 건, 파일명을 나열하는 게 아니고, ‘파일 내용 출력’ 이었다. 어디가 잘못된 걸까?
find . -type f
명령은, 그 결과를 텍스트로 출력을 한다. 물론, 그 결과값이 ‘경로’의 형식을 띠고는 있으나, 디렉토리를 뜻하지는 않고, 그냥 문자열일 뿐이다.
또, cat 의 man page 에는 이런 내용이 초반부에 나와있다.
DESCRIPTION Concatenate FILE(s) to standard output. With no FILE, or when FILE is -, read standard input.
파이프로 넘어온 데이터는 Standard Input 이고, 그냥 문자열이다. 파일이 아니다.
파일이 아니므로, 따라서 cat 은 받은 문자열을 그대로 출력해주는 역할 밖에 하지 못한다.
그냥 간단히 생각을 해본다면, cat ./deleteme.txt
이 되어 이 파일을 출력해줄 듯 한데, cat 은 이걸 파일명으로 인식하지 못한다.
큰 의미는 없지만, 아래 두 명령은 같은 결과를 보여준다.
$ find . -type f | cat $ find . -type f | cat -
리눅스 코맨드 라인 명령어중 일부는, ‘-‘ 기호를 써서, Standard Input 을 받아들일 수 있음을 명시해줄 수 있다. 허나, – 를 쓰든 안쓰든, 받은 결과는 문자열이다.
살짝 이상한데, 이건 또 된다. (다만, find 결과가 딱 하나라는 전제 하에)
cat $(find . -maxdepth 1 -name '.bash_aliases') ... .bash_aliases 내용 출력
하지만, 순서를 바꾸고 파이프라인을 쓰면 안된다.
find . -maxdepth 1 -name '.bash_aliases' | cat ./.bash_aliases
이 때 cat
은, 앞에서 넘어온 개체가 파일이 아닌 Standard Input 이기 때문에, 그것을 그대로 출력한다. 그렇게 설계가 되었기에 저 결과는 당연하다.
살짝 다른 얘기지만, cat
을 사용하여 파일이 아닌 일반 문자열을 출력하고자 한다면, Here String 을 사용해야만 한다.
# 이건 오류 cat "일반 문자열" cat: '일반 문자열': 그런 파일이나 디렉터리가 없습니다 # 이건 성공 cat <<< "일반 문자열" 일반 문자열
다시 원래 문제로 돌아와서, ‘문자열’을 ‘경로’로 인식하게 해주려면??? 자, 뭘 어떻게 해야 되는거냐??
게다가, find 로 받은 결과가 한개가 아니고 복수일 땐?? 이걸 어떻게 해줘야 하나?
여기서 xargs 가 등장한다.
$ find . -type f | xargs cat
이제 내가 원했던 결과를 얻을 수 있게 됐다.
xargs 의 형식은 다음과 같다.
xargs [options] [command [initial-arguments]]
위 명령을 해석해보자면, find 결과를 Standard Input 으로 받아서, 그걸 일련의 리스트(list, 즉 iterable)로 만든 뒤, 뒤에 나오는 command, 즉 cat 에 인수로 넘겨준다.
간단히 말해, cat <find 로 나온 파일명>
을 find 결과 갯수만큼 실행하게 된다.
cat 은 이때 넘겨받은 인수를, Standard Input 이 아닌, 일반 인수, 당연히 경로로 인식을 한다.
여기서 xargs 가 하는 일은, for loop 와 동일하다. Python 으로 구현해보면 이렇다.
def xargs(func, list_of_names): for name in list_of_names: func(name) aa = ['파이썬', '펄', '자바', 'C#'] xargs(print,aa) 파이썬 펄 자바 C#
이런 식이다.
여기선 aa 라는 제대로된 리스트를 만들어 넘겨줬지만, 실제로 xargs 는 리스트를 만드는 일까지 해준다.
find 를 예로 들면, find 의 결과는 개행문자(newline, \n)로 구분되어 출력된다. xargs 는 이것을 받아 구분자(delimiter, 기본값 \n)를 개행문자로 설정한뒤, 리스트화 하여 할당된 명령어에 넘겨준다.
위의 예를 다시 제대로 구성해보면,
find . -type f | xargs cat
이렇게 된다.
물론, 위 명령의 결과는.. 모든 파일을 cat 으로 보여주기 때문에 중간에 Ctrl-C 를 눌러야만 한다.
정리하자면, xargs 는
- Std. Input 으로 넘겨받은 받은 문자열을 리스트화하고, 이 인수를 명령어에 하나씩 대입하며 반복명령을 수행한다. (for loop)
- 리스트화 할 때, 기본 delimiter 는 \n 이다. (물론, 바꿀 수도 있다.)
이번엔 제대로 정리를 한 듯 한데..
나중에 보면 또 뭔가 엉뚱한 소리가 있으려나?