pipeline, 또 다시 한번 도전: xargs 사용법 – 수정본.

# 수정본.
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 이다. (물론, 바꿀 수도 있다.)

이번엔 제대로 정리를 한 듯 한데..
나중에 보면 또 뭔가 엉뚱한 소리가 있으려나?

Author: 아무도안

안녕하세요. 글 남겨주셔서 고맙습니다.