...
xargs 명령어
- 기본적인 명령어 뒤에 파이프로 추가하여 사용
- 파이프 이전에 명령을 인자로 받아 명령어를 실행하는 구조
xargs 유틸리티를 사용하여 표준 입력에서 명령을 작성하고 실행할 수 있다.
xargs를 사용하는 가장 기본적인 예는 pipe to xargs를 사용하여 공백으로 구분된 여러 문자열을 전달하고 해당 문자열을 인수로 사용할 명령을 실행하는 것이다.
# file1 file2 file3 이라는 문자열을 touch 의 인수로 넘겨주어,
# touch file1 file2 file3 명령을 수행한것과 같은 결과를 준다. (빈파일 3개 생성)
echo "file1 file2 file3" | xargs touch
xargs 옵션
-a : 표준 입력 대신 파일에서 항목을 읽음, 이 옵션을 사용하여 명령을 실행하면 stdin(표준 스트림)은 변경되지 않는다. 그렇지 않으면 stdin이 /dev/null에서 리다이렉션 됨
-O : 공백이나 특수문자를 찾을때 사용 (문자를 그대로 사용)
(Ex. find /opt -name “*.[ch]” | xargs touch -> 여기서 파일 이름에 공백이 있을 경우 각각 분리된 파일로 넘겨지는데 find /opt -name “*.[ch]” -print0 | xargs -O touch 형식으로 사용하면
-print0 은 파일 사이의 공백을 \0으로 분리자로 출력하고 xargs에서는 \0으로 표시된 분리자를 인식하여 하나의 파일이름으로 인식하고 다음 인자로 넘어간다)
-d : 입력된 문자를 그대로 사용한다 (따음표, 백슬래쉬 같은 특수문자), 단순히 문자가 스페이스 같은걸로 분리되어 있을때 사용가능 하지만 다른 인수와 같이 처리되는 데는 사용 불가능
-n : 지정된 숫자만큼 행을 출력 (앞에서 들어오는 인자의 수를 제한할 수 있다. 앞에서 5개의 표준 입력이 만들어져도 -n 으로 지정한 숫자만큼의 매개변수가 넘어온다.)
-p : 사용자에게 각 명령 행을 실행할지 여부와 터미널에서 행을 읽는거에 대한 여부를 묻는다 ( yes , no 지정)
-P : 하나의 명령에 프로세스 지정, -n 옵션과 같이 사용 ( -n으로 10개의 출력을 한다면 -p를 사용시 -n만큼의 프로세스가 실행, -p를 0으로 지정하면 한번에 사용할수 있는 프로세스를 모두 사용)
-t : xargs를 통해 구성된 명령어를 표준 에러로 출력 (실행된 커먼드가 무엇인지 표시하므로 디버깅과정에 유용)
- s : 한 라인에 들어갈 수 있는 문자열 수를 지정, 기본적으로 128k 안으로 문자열을 만들어 하나의 명령을 실행하나 해당 옵션은 최대 1024k까지 사용가능하게 한다.
-x : -s 로 지정한 크기가 초과되면 종료시킨다
--show-limits : xargs의 버퍼 크기 선택 및 -s 옵션에 대한 길이 제한을 출력
-E : 문자열 끝을 eof-str로 설정한다.
-I(i) : xargs에 전달된 라인 전체를 뒤에 나오는 명령어의 인자로 사용 (디폴트로 라인 전체를 의미하는 기호는 {} 이다)
find . -name “*.c”” | xargs -i {} sh -c ‘echo -n {} >> c_file.txt; stat -c %Y {} >> c_file.txt’
# -> 하위 폴더에서 모든 C로 끝나는 파일들을 찾아 파일이름과 날짜롤 c.file.txt에 저장
-l(L) : 해당 명령을 사용하면 명령어 뒤에 공백이 있으면 다음 행으로 인식하는게 아닌 다음줄에 입력 라인에 있어도 논리적으로 이어지게 한다.
-l 옵션을 사용하면 읽어들이는 각 행은 내부적으로 버퍼링이 된다.
-l 옵션만 사용하는 경우 허용하는 버퍼의 상한이 있어 제한이 걸리는데 (대량의 파일이나 행을 읽어들이는 경우)
-s 옵션을 함께 사용하면 -s 옵션에 지정된 만큼 버퍼 크기를 늘릴 수 있으며 매우 긴 행이 발생되지 않도록 할 수 있다
xargs 예제
# /tmp 디랙토리 아래 core라는 파일을 찾아 삭제
$ find /tmp -name core -type f -print | xargs rm -f
# ls를 이용하여 text파일을 모두 읽어와 하나의 파일로 병합
$ ls *.txt | xargs cat >> abc.txt
# 디렉토리에서 txt파일을 우선 찾은 다음 이름에 abcd를 포함하는 파일을 또 찾음
$ find ~/ -type f | grep -H “*.txt$” | xargs grep -H “abcd”
# 파일안에 url이 있을 경우 해당 인자들을 모두 wget으로 넘겨 다운받는다
$ cat url-list.txt | xargs wget -c
# 모든 jpg파일을 찾아 images.tar.gz로 압축
$ find / -name “*.jpg” -type -f -print | xargs tar -cvzf images.tar.gz
# ls 로 출력된 모든 이미지를 하나씩 인자로 받아 외장하드로 복사
$ ls *.jpg | xargs -n1 -I{} cp {} /external-hard-drive/tmp
xargs 와 파이프 의 오묘한 차이점 완벽 정리
다음 명령어를 실행해보자.
$ echo "err" > err.txt # "err" 문자열을 err.txt파일에 저장
$ echo err.txt | cat
$ echo err.txt | xargs cat
똑같이 전의 명령 결과를 cat의 재료로서 파이프로 던져준건데, 왜 이런 차이가 날까?
파이프 '|' 는 앞의 결과를 인자로 받는게 아니다.
파이프 '|' 는 앞의 표준출력을 다음 프로그램의 표준입력으로 연결하는 것이다.
파이프 '|' 와의 조합으로 표준출력을 다음 프로그램의 '인자'로 넘길려면 xargs 를 사용한다.
xargs 프로그램은 실행할 대상프로그램을 인자로 받고, xargs 프로그램의 표준입력을 실행 대상프로그램의 인자로 전달하여 실행한다.
여기까지는 기본으로 아는 사양이다.
하지만 글 로써만 알지 정확히 리눅스의 표준입력 과 인자 의 차이에 대해서 매우 모호한 감이 있다.
그럼 무엇이 표준입력이고 무엇이 인자인가?
안타깝게도 터미널에서 이 둘을 확연히 구분하는 것은 어렵다. 이 둘은 명령어에 따라 서로 섞어 쓰기 때문이다.
이는 사용자의 편의성을 위해서다. 예를 들어,
$ cat err.txt
# "err" (파일내용)
$ cat "err.txt"
# "err" (파일내용)
$ cat < err.txt
# "err" (파일내용)
이 세 명령어는 똑같은 동작을 한다.
파일명에 따옴표를 치든 안치든, 입력 재지정을 하든 안하든 결과는 똑같다는 것이다.
이는 CLI명령어를 입력하고 실행하는데는 매우 편리한 장점이 있지만, 앞서 말했듯이 구분이 모호해 햇깔릴 수 있다는 단점이 존재한다.
예를들어 프로그래밍 언어에선 변수명은 그냥 써주면 되고 문자열타입이면 반드시 따옴표를 쳐주어 이 둘을 구분한다.
하지만 리눅스 쉘에서는 따옴표를 치든 안치든 파일명으로서 cat 의 인자로서 쓰이기 때문에, cat 이 문자열을 받는건지 파일명을 받는건지 정확히 알지 못한채 그냥 쓰는 경향이 있다.
그렇다면 cat명령어에 문자열 자체를 출력하는 방법이 있을까?
<<< 뒤에는 문자열이나 변수가 바로 붙는다.
< 는 표준입력 리다이렉션으로서 아시다시피 뒤에 파일명이 붙는다.
<<< 는 here string으로서 문자열 자체를 전달한다고 보면 된다.
$ cat 뒤에는 파일명이 와서 그 파일의 내용물을 출력하는 명령어인데 문자열 그대로를 출력해 버린 것이다.
이것이 바로 표준 입력이다.
모호했던 표준입력과 인자의 차이점에 대해서 전구가 떠올랐다.
다시 돌아와서 첫번째 예제를 보자.
# echo err.txt 명령 결과인 "err.txt" 문자열이 표준입력으로서 파이프로 넘겨져 cat에서 표준입력 즉, 문자열 그대로를 출력
$ echo err.txt | cat == $ cat <<< "err.txt"
# echo err.txt 명령 결과인 "err.txt" 문자열이 cat의 인자로서 xargs에 담겨져 cat [파일명] 명령 실행
$ echo err.txt | xargs cat == $ cat err.txt
대충 정리하자면 이렇게 잡을 수 있다.
이제 인자와 표준입력 둘의 차이점은 단순히 단어 생김새 뿐만 아니라 출력결과도 확연히 다르다는게 보인다.
하지만 보다 정확한 동작 원리가 필요하다.
이번에는 정말 많이 쓰이는 grep 명령어을 이용해서 확인해 보자.
$ grep [패턴] [파일명] # 파일명의 파일내용을 뒤져 내가 찾고자하는 내용을 뽑아 출력
$ find -name "output.txt" # output.txt 파일명을 찾는다.
./output.txt # 찾았다
# find와 grep을 동시에 쓰는 아주 많이 보는 패턴이다.
# "output.txt" 라는 파일명을 find해 output.txt 파일을 xargs로 넘겨져 grep의 인자로서 실행된다.
# 즉 grep "qa" output.txt 와 같다
$ find -name "output.txt" | xargs grep "qa"
qa :0 20222-02-03 18:16 (:0)
qa :0 20222-02-03 18:16 (:0)
qa :0 20222-02-03 18:16 (:0)
# xargs를 빼자 명령 반응이 없다.
$ find -name "output.txt" | grep "qa" # ??? 반응이 없다
# output.txt파일의 내용을 출력(cat)하고 그 내용을 파이프를 통해 grep의 표준입력으로 넘긴다
# 표준입력 문자열을 받은 grep은 그 문자열을 탐색하여 결과를 출력한다
$ cat "output.txt" | grep "qa"
qa :0 20222-02-03 18:16 (:0)
qa :0 20222-02-03 18:16 (:0)
qa :0 20222-02-03 18:16 (:0)
자, 대망의 마무리이다.
이 세 명령어의 차이점만 알면 당신은 xargs와 파이프의 오묘한 차이를 마스터한 것이다.
$ find -name "output.txt" | xargs grep "qa" # 파일을 find한 결과 파일명을 인자로 받아 grep 실행
$ find -name "output.txt" | grep "qa" # 파일을 find한 결과 파일명을 표준입력으로 받아 grep 실행
$ cat "output.txt" | grep "qa" # 파일을 cat한 결과 파일내용을 표준입력으로 받아 grep 실행
먼저 xargs부분은 내치고 find 와 cat 명령 두 부분을 보자.
둘은 똑같이 파이프를 통해 표준출력을 grep의 표준입력으로서 넘겨주지만, 둘은 완전히 반대의 결과를 준다.
짚고넘어가자면 파이프를 통해 find는 파일명 자체를 넘겨주었고, cat은 파일의 파일내용물을 넘겨주었다.
find 했을때 위의 코드 주석에선 반응이 없다고 설명했지만 정확히 말하자면 명령을 실행되었고 단지 "qa"라는 패턴검색을 찾지 못해서 아무것도 출력을 하지 않은 것이다.
반면 cat 했을때는 패턴검색이 되었다.
이는 당연한 결과이다. 정리하자면,
"output.txt" 파일명 문자열 자체를 grep의 표준입력으로 넘겨주었으니 당연히 "output.txt" 에서 "qa" 라는 문자는 없으니 명령반응이 없는거고,
output.txt 파일의 "파일내용"을 grep의 표준입력으로 넘겨주었으니, grep은 파일내용을 훑어봐 "qa" 라는 문자가 있는 행들을 결과로 출력 한 것이다.
그리고 xargs 같은 경우 grep의 인자로서 파일명이 들어가서, 원래 grep의 명령실행인 $ grep [패턴] [파일명] 이 실행되 정상적으로 패턴검색이 되는 것이다.
$ find -name "output.txt" | xargs grep "qa" == $ grep "qa" output.txt
이 세 명령어를 그림으로 정리하자면 이렇다.
더 나아가서 리다이렉션 입출력 재지정에 대해서도 비교해 본다.
# output.txt를 표준입력 재지정하여 grep에 넘긴다.
# 즉, output.txt의 파일내용을 꺼내 grep 검색을 한다는 말이다.
$ grep "qa" < output.txt
# output.txt의 파일내용을 cat해서 꺼내 파이프로 파일내용을 보내 grep 검색한다.
# 따지고보면 위 명령어와 같다.
$ cat output.txt | grep "qa"
# 앞서 배웠던 <<< 연산자다.
$ grep "qa" <<< output.txt # output.txt 문자열에 qa가 없으니 명령 반응이 없다.
$ program < file_name
file_name의 파일 내용을 program의 표준 입력으로 사용하겠다 라는 의미이다.
즉, grep은 "output.txt"를 문자열로서 받아들이는게 아니라 파일의 "파일내용 문자열"로서 받아들여 명령이 실행된 것이다.
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.