명령에서 파일을 사용하고 출력을 자르지 않고 동일한 파일로 리디렉션하려면 어떻게해야합니까?


98

기본적으로 파일에서 입력 텍스트로 가져 와서 해당 파일에서 한 줄을 제거하고 출력을 동일한 파일로 다시 보내고 싶습니다. 이 선을 따라 가면 더 명확 해집니다.

grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name > file_name

그러나 이렇게하면 빈 파일이 생깁니다. 이견있는 사람?


답변:


84

bash가 먼저 리디렉션을 처리 한 다음 명령을 실행하기 때문에 그렇게 할 수 없습니다. 따라서 grep이 file_name을 볼 때 이미 비어 있습니다. 그래도 임시 파일을 사용할 수 있습니다.

#!/bin/sh
tmpfile=$(mktemp)
grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name > ${tmpfile}
cat ${tmpfile} > file_name
rm -f ${tmpfile}

이와 같이 tmpfilemktemp 을 만드는 데 사용 하는 것을 고려 하지만 POSIX가 아니라는 점에 유의하십시오.


47
그렇게 할 수없는 이유 : bash는 먼저 리디렉션을 처리 한 다음 명령을 실행합니다. 따라서 grep이 file_name을 볼 때 이미 비어 있습니다.
glenn jackman

1
@glennjackman : "프로세스 리디렉션이란>의 경우 파일을 열고 지우고 >>의 경우에만 열림을 의미합니까?
Razvan

2
예, 그러나이 상황에서 주목할 점은 >리디렉션이 파일을 열고 쉘이 시작 되기 전에 잘립니다 grep.
glenn jackman

1
임시 파일을 사용하고 싶지 않다면 내 대답을 참조하십시오 .이 댓글을 찬성하지 마십시오.
잭 모리스

대신 명령을 사용한 대답을sponge 받아 들여야합니다.
vlz

96

이런 종류의 작업 에는 스폰지 를 사용하십시오 . moreutils의 일부입니다.

다음 명령을 시도하십시오.

 grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name | sponge file_name

4
답변 해주셔서 감사합니다. 도움이 될 수있는 추가 기능으로 Mac에서 homebrew를 사용하는 경우 brew install moreutils.
Anthony Panozzo 2013

2
또는 sudo apt-get install moreutilsDebian 기반 시스템에서.
Jonah

3
제길! moreutils =) 멋진 프로그램을 소개해 주셔서 감사합니다!
netigger 2015 년

구조에 대한 moreutils 정말 감사합니다! 보스처럼 스펀지!
aqquadro

3
주의 할 점은 "sponge"는 파괴적이므로 명령에 오류가 있으면 입력 파일을 지울 수 있습니다 (스펀지를 처음 시도했을 때처럼). 명령이 작동하는지 확인하고, 명령이 작동하도록 반복하려는 경우 입력 파일이 버전 제어를 받고 있는지 확인하십시오.
user107172 dec.

18

대신 sed를 사용하십시오.

sed -i '/seg[0-9]\{1,\}\.[0-9]\{1\}/d' file_name

1
iirc -i는 GNU 전용 확장입니다.
c00kiemon5ter

3
* BSD (따라서 OSX) -i ''에서는 확장이 엄격하게 필수는 아니지만 -i옵션에 몇 가지 인수 가 필요 하다고 말할 수 있습니다 .
tripleee 2011

14

이 간단한 것을 시도하십시오

grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name | tee file_name

이번에는 파일이 비어 있지 않습니다. :) 출력도 터미널에 인쇄됩니다.


1
나는이 해결책을 좋아한다! 터미널에서 인쇄하지 않으려면 출력을 /dev/null또는 유사한 위치 로 리디렉션 할 수 있습니다.
Frozn

4
여기에서 파일 내용도 지워집니다. GNU / BSD 차이 때문입니까? 저는 macOS를 사용 중입니다 ...
ssc

7

동일한 파일에 대한 리디렉션 연산자 ( >또는 >>)를 사용할 수 없습니다. 우선 순위가 더 높고 명령이 호출되기 전에 파일을 생성 / 자르기 때문입니다. 그것을 방지하기 위해 다음과 같은 적절한 도구를 사용해야합니다 tee, sponge, sed -i또는 파일 (예에 대한 결과를 쓸 수있는 다른 도구 sort file -o file).

기본적으로 입력을 동일한 원본 파일로 리디렉션하는 것은 의미가 없으며이를 위해 적절한 내부 편집기 (예 : Ex 편집기 (Vim의 일부))를 사용해야합니다.

ex '+g/seg[0-9]\{1,\}\.[0-9]\{1\}/d' -scwq file_name

어디:

  • '+cmd'/ -c-Ex / Vim 명령 실행
  • g/pattern/d- 전역 (help :g )을
  • -s-무음 모드 ( man ex)
  • -c wq-실행 :write:quit명령

당신은 사용할 수 sed있지만, (이미 다른 답변에서와 같이) 동일을 달성하기 위해 자리에서 ( -i(유닉스 / 리눅스 사이에 다르게 작동 할 수 있습니다) 표준이 아닌 FreeBSD의 확장은) 기본적으로 그것은이다 tream 에드 당사 홈페이지가 아닌 파일 편집기 . 참조 : Ex 모드는 실용적인 용도가 있습니까?


6

하나의 라이너 대안-파일의 내용을 변수로 설정 :

VAR=`cat file_name`; echo "$VAR"|grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' > file_name

4

이 질문이 검색 엔진의 상위 결과이므로 다음은 대신 하위 셸을 사용하는 https://serverfault.com/a/547331 을 기반으로 한 한 줄 입니다 sponge(종종 OS X와 ​​같은 바닐라 설치의 일부가 아님). :

echo "$(grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name)" > file_name

일반적인 경우는 다음과 같습니다.

echo "$(cat file_name)" > file_name

위의 솔루션에는 몇 가지주의 사항이 있습니다.

  • printf '%s' <string>대신에를 사용 echo <string>하여 포함 된 파일 -n이 원치 않는 동작을 일으키지 않도록해야합니다 .
  • 뉴 라인 후행 명령 대체 스트립 ( 이 bash는 같은 포탄의 버그 / 기능입니다 ) 우리는 같은 접미사 문자를 추가해야하므로 x출력과를 통해 외부에 제거 임시 변수의 매개 변수 확장 과 같은${v%x} . .
  • 임시 변수를 사용 하면 현재 쉘 환경 $v에있는 기존 변수의 값이 스톰 핑 $v되므로 이전 값을 유지하려면 전체 표현식을 괄호로 묶어야합니다.
  • bash와 같은 쉘의 또 다른 버그 / 기능은 명령 대체 null가 출력에서 와 같이 인쇄 할 수없는 문자를 제거한다는 것 입니다. 나는 이것을 호출 dd if=/dev/zero bs=1 count=1 >> file_name하고 cat file_name | xxd -p. 그러나 echo $(cat file_name) | xxd -p벗겨집니다. 따라서이 답변은 Lynch가 지적했듯이 이진 파일이나 인쇄 할 수없는 문자를 사용하는 모든 항목에 사용 해서는 안됩니다 .

일반적인 솔루션 (약간 느리고 메모리 집약적이며 여전히 인쇄 할 수없는 문자 제거)은 다음과 같습니다.

(v=$(cat file_name; printf x); printf '%s' ${v%x} > file_name)

https://askubuntu.com/a/752451 에서 테스트 :

printf "hello\nworld\n" > file_uniquely_named.txt && for ((i=0; i<1000; i++)); do (v=$(cat file_uniquely_named.txt; printf x); printf '%s' ${v%x} > file_uniquely_named.txt); done; cat file_uniquely_named.txt; rm file_uniquely_named.txt

다음을 인쇄해야합니다.

hello
world

cat file_uniquely_named.txt > file_uniquely_named.txt현재 쉘에서 호출하는 반면 :

printf "hello\nworld\n" > file_uniquely_named.txt && for ((i=0; i<1000; i++)); do cat file_uniquely_named.txt > file_uniquely_named.txt; done; cat file_uniquely_named.txt; rm file_uniquely_named.txt

빈 문자열을 인쇄합니다.

대용량 파일 (아마 2GB 또는 4GB 이상)에서는 테스트하지 않았습니다.

나는 Hart Simhakos 에게서이 대답을 빌렸다 .


2
물론 대용량 파일에서는 작동하지 않습니다. 이것은 좋은 해결책이 될 수도없고 항상 작동 할 수도 없습니다. 무슨 일이 일어나고 있는지 bash는 먼저 명령을 실행 한 다음 stdout을로드하고 cat첫 번째 인수로 넣습니다 echo. 물론 인쇄 할 수없는 변수는 제대로 출력되지 않고 데이터를 손상시킵니다. 파일을 자신에게 다시 리디렉션하지 마십시오. 좋지 않을 수 있습니다.
Lynch

1

도 있습니다 ed(대안으로 sed -i) :

# cf. http://wiki.bash-hackers.org/howto/edit-ed
printf '%s\n' H 'g/seg[0-9]\{1,\}\.[0-9]\{1\}/d' wq |  ed -s file_name

1

프로세스 대체 를 사용하여 수행 할 수 있습니다. .

bash가 모든 파이프를 비동기식으로 열고 다음을 사용하여 해결해야하기 때문에 약간의 해킹입니다. sleep YMMV 하므로 입니다.

귀하의 예에서 :

grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name > >(sleep 1 && cat > file_name)
  • >(sleep 1 && cat > file_name) grep에서 출력을받는 임시 파일을 만듭니다.
  • sleep 1 입력 파일을 구문 분석하는 grep 시간을 제공하기 위해 1 초 동안 지연
  • 마지막으로 cat > file_name출력을 씁니다.

1

POSIX Awk와 함께 slurp를 사용할 수 있습니다.

!/seg[0-9]\{1,\}\.[0-9]\{1\}/ {
  q = q ? q RS $0 : $0
}
END {
  print q > ARGV[1]
}


1
"slurp"는 "전체 파일을 메모리로 읽는"것을 의미한다는 점을 지적해야합니다. 큰 입력 파일이있는 경우이를 피하는 것이 좋습니다.
tripleee 2011

1

이것은 매우 가능합니다. 출력을 쓸 때 다른 파일에 쓰고 있는지 확인하기 만하면됩니다. 파일 설명자를 연 후 쓰기 전에 파일을 제거하면됩니다.

exec 3<file ; rm file; COMMAND <&3 >file ;  exec 3>&-

또는 한 줄씩 더 잘 이해하려면 :

exec 3<file       # open a file descriptor reading 'file'
rm file           # remove file (but fd3 will still point to the removed file)
COMMAND <&3 >file # run command, with the removed file as input
exec 3>&-         # close the file descriptor

COMMAND가 제대로 실행되지 않으면 파일 내용을 잃게되기 때문에 여전히 위험한 일입니다. COMMAND가 0이 아닌 종료 코드를 반환하면 파일을 복원하여 완화 할 수 있습니다.

exec 3<file ; rm file; COMMAND <&3 >file || cat <&3 >file ; exec 3>&-

사용하기 쉽도록 쉘 함수를 정의 할 수도 있습니다.

# Usage: replace FILE COMMAND
replace() { exec 3<$1 ; rm $1; ${@:2} <&3 >$1 || cat <&3 >$1 ; exec 3>&- }

예 :

$ echo aaa > test
$ replace test tr a b
$ cat test
bbb

또한 이렇게하면 원본 파일의 전체 복사본이 유지됩니다 (세 번째 파일 설명자가 닫힐 때까지). Linux를 사용하고 있고 처리중인 파일이 너무 커서 디스크에 두 번 넣을 수없는 경우 이미 처리 된 파일을 할당 해제하면서 파일을 지정된 명령에 블록별로 파이프하는 이 스크립트 를 확인할 수 있습니다. 블록. 항상 그렇듯이 사용 페이지의 경고를 읽으십시오.


0

이 시도

echo -e "AAA\nBBB\nCCC" > testfile

cat testfile
AAA
BBB
CCC

echo "$(grep -v 'AAA' testfile)" > testfile
cat testfile
BBB
CCC

간단한 설명이나 의견이 도움이 될 수 있습니다.
Rich

내가 문자열 추정은 리디렉션 연산자 앞에 실행할 수 있기 때문에 작업, 생각,하지만 난 정확히 모른다
Виктор Пупкин

0

다음은 sponge요구하지 않고 수행하는 것과 동일한 작업을 수행합니다 moreutils.

    shuf --output=file --random-source=/dev/zero 

--random-source=/dev/zero부분 shuf은 셔플 링을 전혀하지 않고 작업을 수행하도록 속이기 때문에 입력을 변경하지 않고 버퍼링합니다.

그러나 성능상의 이유로 임시 파일을 사용하는 것이 가장 좋습니다. 그래서 여기에 제가 작성한 함수가 있습니다. 이것은 당신을 위해 일반화 된 방식으로 할 것입니다 :

# Pipes a file into a command, and pipes the output of that command
# back into the same file, ensuring that the file is not truncated.
# Parameters:
#    $1: the file.
#    $2: the command. (With $3... being its arguments.)
# See https://stackoverflow.com/a/55655338/773113

function siphon
{
    local tmp=$(mktemp)
    local file="$1"
    shift
    $* < "$file" > "$tmp"
    mv "$tmp" "$file"
}

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