명령 대체 : 줄 바꿈에서 분할하지만 공백은 아닙니다.


30

나는이 문제를 여러 가지 방법으로 해결할 수 있다는 것을 알고 있지만 bash 내장 만 사용하여 해결할 수있는 방법이 있는지, 그렇지 않은 경우 가장 효율적인 방법은 무엇인지 궁금합니다.

내용이 같은 파일이 있습니다

AAA
B C DDD
FOO BAR

즉, 여러 줄이 있고 각 줄에는 공백이 있거나 없을 수 있습니다. 나는 같은 명령을 실행하고 싶다

cmd AAA "B C DDD" "FOO BAR"

내가 사용 cmd $(< file)하면 얻을

cmd AAA B C DDD FOO BAR

내가 사용 cmd "$(< file)"하면 얻을

cmd "AAA B C DDD FOO BAR"

각 줄을 정확히 하나의 매개 변수로 처리하려면 어떻게합니까?


답변:


26

포터블 :

set -f              # turn off globbing
IFS='
'                   # split at newlines only
cmd $(cat <file)
unset IFS
set +f

또는 서브 쉘을 사용하여 IFS및 옵션을 로컬로 변경하십시오.

( set -f; IFS='
'; exec cmd $(cat <file) )

쉘은 큰 따옴표가 아닌 변수 또는 명령 대체의 결과에서 필드 분할 및 파일 이름 생성을 수행합니다. 따라서을 사용하여 파일 이름 생성을 끄고 개행 만 별도의 필드로 만들도록 set -f필드 분할을 구성 IFS해야합니다.

bash 또는 ksh 구문으로는 얻을 것이 많지 않습니다. IFS함수에 로컬을 만들 수는 있지만 사용할 수는 없습니다 set -f.

bash 또는 ksh93에서는 여러 명령에 필드를 전달해야하는 경우 필드를 배열에 저장할 수 있습니다. 어레이를 구축 할 때 확장을 제어해야합니다. 그런 다음 "${a[@]}"단어 당 하나씩 배열의 요소로 확장됩니다.

set -f; IFS=$'\n'
a=($(cat <file))
set +f; unset IFS
cmd "${a[@]}"

10

임시 배열로이 작업을 수행 할 수 있습니다.

설정:

$ cat input
AAA
A B C
DE F
$ cat t.sh
#! /bin/bash
echo "$1"
echo "$2"
echo "$3"

배열을 채우십시오 :

$ IFS=$'\n'; set -f; foo=($(<input))

배열을 사용하십시오.

$ for a in "${foo[@]}" ; do echo "--" "$a" "--" ; done
-- AAA --
-- A B C --
-- DE F --

$ ./t.sh "${foo[@]}"
AAA
A B C
DE F

임시 변수가 없으면이를 수행하는 방법을 알 수 없습니다- IFS변경이 중요하지 않은 cmd경우 :

$ IFS=$'\n'; set -f; cmd $(<input) 

해야합니다.


IFS항상 혼란스러워합니다. IFS=$'\n' cmd $(<input)작동하지 않습니다. IFS=$'\n'; cmd $(<input); unset IFS작동합니다. 왜? 내가 사용할 것 같아요(IFS=$'\n'; cmd $(<input))
Old Pro

6
@OldPro IFS=$'\n' cmd $(<input)IFS의 환경 에서만 설정되므로 작동하지 않습니다 cmd. $(<input)할당 IFS이 수행 되기 전에 명령이 확장되도록 명령이 확장됩니다 .
질 'SO-정지 존재 악마'

8

이 작업을 수행하는 정식 방법 bash은 다음과 같습니다.

unset args
while IFS= read -r line; do 
    args+=("$line") 
done < file

cmd "${args[@]}"

또는 bash 버전에 다음이있는 경우 mapfile:

mapfile -t args < filename
cmd "${args[@]}"

맵 파일과 while-read 루프와 one-liner 사이에서 찾을 수있는 유일한 차이점

(set -f; IFS=$'\n'; cmd $(<file))

전자는 빈 줄을 빈 인수로 변환하지만 한 줄은 빈 줄을 무시합니다. 이 경우, 한 줄짜리 동작은 내가 선호하는 것이므로 컴팩트하면 두 배의 보너스입니다.

사용 IFS=$'\n' cmd $(<file)하지만 $(<file)명령 행을 구성하기 전에 해석 되기 때문에 작동하지 않습니다 IFS=$'\n'.

내 경우에없는 작업을 수행하지만, 지금은 많은 도구와 함께 종료 라인을 지원하는 것을 배운 null (\000)대신 newline (\n)하는 이러한 상황의 일반적인 소스입니다 파일 이름, 말하자면, 처리 할 때이 쉽게 많이 만들어 않습니다 :

find / -name '*.config' -print0 | xargs -0 md5

정규화 된 파일 이름 목록을 globbing 또는 보간 또는 기타 사항없이 md5에 인수로 제공합니다. 내장되지 않은 솔루션으로 연결됩니다.

tr "\n" "\000" <file | xargs -0 cmd

공백도있는 행은 캡처하지만 빈 행은 무시합니다.


사용 cmd $(<file)인용 (분할 단어 배쉬의 능력을 사용)하지 않고 값은 항상 위험한 내기이다. 줄이 있으면 *셸에 의해 파일 목록으로 확장됩니다.

3

bash 내장을 사용하여 mapfile파일을 배열로 읽을 수 있습니다

mapfile -t foo < filename
cmd "${foo[@]}"

또는 테스트되지 않은 xargs경우

xargs cmd < filename

mapfile 문서에서 : "mapfile은 일반적이거나 이식 가능한 쉘 기능이 아닙니다". 그리고 실제로 내 시스템에서 지원되지 않습니다. xargs도움이되지 않습니다.
Old Pro

당신은 필요 xargs -d또는xargs -L
제임스 Youngman에게

@James, 아니요, -d옵션 이 없으며 xargs -L 1한 줄에 한 번 명령을 실행하지만 여전히 공백으로 args를 분할합니다.
Old Pro

1
@OldPro, "공통 또는 휴대용 쉘 기능"대신 "bash 내장 기능 만 사용하여 수행하는 방법"을 요청했습니다. bash 버전이 너무 오래된 경우 업데이트 할 수 있습니까?
glenn jackman

mapfile빈 줄을 배열 항목으로 가져 와서 IFS메소드가 수행하지 않기 때문에 매우 편리합니다 . IFS연속 줄 바꿈을 단일 구분 기호로 처리합니다 ... 명령을 알지 못했기 때문에 제시해 주셔서 감사합니다 (OP의 입력 데이터와 예상 명령 줄을 기반으로 실제로 빈 줄을 무시하고 싶어합니다).
Peter.O

0
old=$IFS
IFS='  #newline
'
array=`cat Submissions` #input the text in this variable
for ...  #use parts of variable in the for loop
... 
done
IFS=$old

내가 찾을 수있는 가장 좋은 방법. 그냥 작동합니다.


IFS우주로 설정하면 왜 작동 합니까? 하지만 문제는 우주로 나뉘 지 않는 것 입니까?
RalfFriedl

0

파일

개행으로 파일을 분할하는 가장 기본적인 루프 (휴대용)는 다음과 같습니다.

#!/bin/sh
while read -r line; do            # get one line (\n) at a time.
    set -- "$@" "$line"           # store in the list of positional arguments.
done <infile                      # read from a file called infile.
printf '<%s>' "$@" ; echo         # print the results.

다음과 같이 인쇄됩니다.

$ ./script
<AAA><A B C><DE F>

예, 기본 IFS = spacetabnewline입니다.

작동하는 이유

  • IFS는 셸에서 입력을 여러 변수 로 분할하는 데 사용됩니다 . 단지 거기이기 때문에 하나의 변수가 더 분할 쉘에 의해 수행되지 않습니다. 따라서 변경이 IFS필요 없습니다.
  • 예, 선행 및 후행 공백 / 탭이 제거되고 있지만이 경우에는 문제가되지 않습니다.
  • 아니요, 확장이 인용되지 않았 으므로 글 로빙 이 수행되지 않습니다 . 따라서 필요 하지 않습니다.set -f
  • 사용되거나 필요한 유일한 배열은 배열과 같은 위치 매개 변수입니다.
  • -r(원시) 옵션은 대부분의 백 슬래시의 제거를 방지하는 것입니다.

그것은 것입니다 하지 분할 및 / 또는 로빙이 필요한 경우 작동합니다. 이러한 경우 더 복잡한 구조가 필요합니다.

(여전히 휴대용) 필요한 경우 :

  • 선행 및 후행 공백 / 탭을 제거하지 말고 다음을 사용하십시오. IFS= read -r line
  • 일부 문자에서 줄을 var로 분할하려면 다음을 사용하십시오 IFS=':' read -r a b c.

다른 문자로 파일 을 분할하십시오 (휴대용이 아니며 ksh, bash, zsh와 함께 작동).

IFS=':' read -d '+' -r a b c

확장

물론 질문의 제목은 공백으로 분할하지 않고 줄 바꿈으로 명령 실행을 분할하는 것입니다.

쉘에서 분리하는 유일한 방법은 따옴표없이 확장을 두는 것입니다.

echo $(< file)

이는 IFS의 가치에 의해 통제되며, 인용되지 않은 확장에서는 globbing도 적용됩니다. 그 일을하기 위해서는 다음이 필요합니다.

  • 새로운 라인으로 설정 IFS 만은 , 줄 바꿈에 분할 만 얻을 수 있습니다.
  • globbing shell 옵션을 설정 해제하십시오 set +f.

    + f IFS = ''cmd $ (<파일) 설정

물론, 이것은 IFS의 가치를 바꾸고 나머지 스크립트를 위해 글 로빙하는 것입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.