[unix] 머리와 꼬리를 사용하여 다른 선 세트를 잡고 동일한 파일로 저장

그래서 이것은 숙제를위한 것이지만 구체적인 숙제에 대해서는 묻지 않을 것입니다.

하나의 파일에서 다른 라인 세트를 가져 오기 위해 head와 tail을 사용해야합니다. 따라서 6-11 행과 19-24 행과 같이 두 파일을 다른 파일에 저장하십시오. 나는 다음과 같은 추가를 사용하여 이것을 할 수 있다는 것을 안다.

head -11 file|tail -6 > file1; head -24 file| tail -6 >> file1.

그러나 나는 우리가해야한다고 생각하지 않습니다.
head 및 tail 명령을 결합한 다음 파일에 저장할 수있는 특정 방법이 있습니까?



답변

다음과 같은 구문 head{ ... ; }사용하여 명령을 그룹화하면 단독 및 기본 산술로 수행 할 수 있습니다

{ head -n ...; head -n ...; ...; } < input_file > output_file

모든 명령이 동일한 입력을 공유합니다 ( @mikeserv 덕분에 ).
6-11 행과 19-24 행을 얻는 것은 다음과 같습니다.

head -n 5 >/dev/null  # dump the first 5 lines to `/dev/null` then
head -n 6             # print the next 6 lines (i.e. from 6 to 11) then
head -n 7 >/dev/null  # dump the next 7 lines to `/dev/null` ( from 12 to 18)
head -n 6             # then print the next 6 lines (19 up to 24)

따라서 기본적으로 다음을 실행합니다.

{ head -n 5 >/dev/null; head -n 6; head -n 7 >/dev/null; head -n 6; } < input_file > output_file


답변

{ … }그룹화 구문을 사용하여 경로 재 지정 연산자를 복합 명령에 적용 할 수 있습니다 .

{ head -n 11 file | tail -n 6; head -n 24 file | tail -n 6; } >file1

첫 번째 M + N 행을 복제하고 마지막 N 만 유지하는 대신 첫 번째 M 행을 건너 뛰고 다음 N을 복제 할 수 있습니다. 이는 큰 파일에서 훨씬 빠릅니다 . 것을주의 +N의 인수가 tail스킵하는 라인의 수 있지만, 하나가 아닌 플러스 있음 – 1부터 번호 라인을 인쇄 할 첫 번째 행의 수입니다.

{ tail -n +6 file | head -n 6; tail -n +19 file | head -n 6; } >file1

어느 쪽이든, 출력 파일은 한 번만 열리지 만 각 스 니펫에서 추출 할 때마다 입력 파일이 한 번 순회됩니다. 입력을 그룹화하는 것은 어떻습니까?

{ tail -n +6 | head -n 6; tail -n +14 | head -n 6; } <file >file1

일반적으로 이것은 작동하지 않습니다. (적어도 입력이 일반 파일 인 경우 일부 시스템에서 작동 할 수 있습니다.) 왜 그렇습니까? 입력 버퍼링 때문에 . 를 포함한 대부분의 프로그램 tail은 입력을 바이트 단위로 읽지 않지만 한 번에 몇 킬로바이트를 읽습니다. 속도가 빠르기 때문입니다. 따라서 tail몇 킬로바이트를 읽고 시작 부분을 조금 건너 뛰고 조금 더 지나간 다음 head중지합니다. 그러나 읽은 내용은 읽히고 다음 명령에는 사용할 수 없습니다.

또 다른 방법head파이프 를 사용 /dev/null하여 줄을 건너 뛰는 것입니다.

{ head -n 5 >/dev/null; head -n 6; head -n 7 >/dev/null; head -n 6; } <file >file1

다시 말하지만 버퍼링으로 인해 작동하지 않을 수 있습니다. head입력이 일반 파일에서 나올 때 GNU coreutils (비 내장 Linux 시스템에 있는 명령) 의 명령 으로 작동 합니다. 이 구현이 head원하는 것을 읽은 후에는 파일 위치 를 출력하지 않은 첫 바이트로 설정하기 때문입니다. 입력이 파이프 인 경우 작동하지 않습니다.

파일에서 여러 줄의 행을 인쇄하는 간단한 방법은 sed 또는 awk 와 같은보다 일반적인 도구를 호출하는 것 입니다. (이 속도는 느려질 수 있지만 매우 큰 파일에만 중요합니다.)

sed -n -e '6,11p' -e '19,24p' <file >file1
sed -e '1,5d' -e '12,18d' -e '24q' <file >file1
awk '6<=NR && NR<=11 || 19<=NR && NR<=24' <file >file1
awk 'NR==6, NR==11; NR==19, NR==24' <file >file1


답변

나는 당신이 머리와 꼬리를 사용해야한다고 말했지만 sed는 분명히 작업을위한 가장 간단한 도구입니다.

$ cat foo
a 1 1
a 2 1
b 1 1
a 3 1
c 3 1
c 3 1
$ sed -ne '2,4p;6p' foo
a 2 1
b 1 1
a 3 1
c 3 1

다른 프로세스를 사용하여 문자열로 블록을 작성하고 sed를 통해 실행할 수도 있습니다.

$ a="2,4p;6p"
$ sed -ne $a foo
a 2 1
b 1 1
a 3 1
c 3 1

-n은 출력을 무시한 다음 범위의 첫 번째 숫자와 마지막 숫자를 쉼표로 구분하여 p로 인쇄 할 범위를 지정합니다.

즉, @don_crissti가 제안한 명령 그룹화를 수행하거나 머리 / 꼬리로 갈 때마다 줄 덩어리를 잡아서 파일을 몇 번 반복 할 수 있습니다.

$ head -4 foo | tail -3; head -6 foo | tail -1
a 2 1
b 1 1
a 3 1
c 3 1

파일에 줄이 많고 블록이 많을수록 sed가 더 효율적입니다.


답변

로에게 sed당신이 할 수 있습니다 :

sed '24q;1,5d;12,18d' <infile >outfile

… 아마도 더 효율적인 솔루션이있을 수 있습니다 head. Don은 이미 그 방법이 잘 작동하는 방법을 이미 보여 주었지만, 나는 그것도 가지고 놀았습니다. 이 특정한 경우를 처리하기 위해 할 수있는 일 :

for   n in 5 6 7 6
do    head -n"$n" >&"$((1+n%2))"
done  <infile >outfile 2>/dev/null

… 반복의 값 이 짝수인지 홀수 인지 에 따라 head4 번이나 호출 합니다 .outfile/dev/null$n

더 일반적인 경우에는 이미 가지고있는 다른 것들과 함께 이것을 조롱했습니다.

somehead()(
### call it like:
### somehead -[repeat] [-][numlines]* <infile >outfile
    set -e -- "${1#-}" "$@"                             #-e for arg validation
    r=; cd -- "${TMP:-/tmp}"                            #go to tmp
    dd bs=4096 of="$$$$" <&4 2>&3 &                     #dd <in >tmpfile &bg
    until [ -s "$$$$" ]; do :; done                     #wait while tmpfile empty
    exec <"$$$$" 4<&-;   rm "$$$$"                      #<tmpfile; rm tmpfile
    [ "$3${1}0" -ne "$3${2#?}0" ]          ||           #validate args - chk $1
            shift "$(((r=-${1:--1})||1))"; shift        #shift 1||2
    while [ "$(((r+=(_n=1))-1))" -ne 0 ]   &&           #while ! $rptmax &&
          IFS= read -r l                   &&           #      ! EOF     &&
          printf "%.$(($1>0?${#l}+1:0))s" "$l           #      ? printf  do
";  do    for n do [ "${n#-}" -gt 0 ]      || exit      #args all -[nums>0]
          head "-n$((${n#-}-_n))" >&"$((n>(_n=0)?1:3))" #head -n?$1 >?[+-]
    done; done                                          #done and done
)   4<&0 3>/dev/null                                    #4<for dd 3>for head

이것은 다음과 같은 일을 할 수 있습니다.

 seq 100 | somehead -1 -5 6 -7 6

… 인쇄 …

6
7
8
9
10
11
19
20
21
22
23
24

첫 번째 인수는 접두사로 시작하는 반복 횟수 -, 또는 실패한 경우에만 a -입니다. 카운트가 제공되면 지정된 횟수만큼 다음 args에 주어진 라인 패턴을 반복하고 그렇게되면 중지합니다.

다음에 나오는 각 인수에 대해 기록해야하는 행 수를 나타 내기 위해 음의 정수를 해석하고에 기록되어야 /dev/null하는 행 수를 나타 내기 위해 양의 정수를 해석합니다 stdout.

따라서 위의 예에서는 처음 5 행을 /dev/null다음에 6 stdout, 다음 7을 /dev/null다시, 다음 6을 다시 한 번에 인쇄합니다 stdout. 마지막 인수에 도달하고 -1반복 횟수를 완전히 순환 하면 종료됩니다. 첫 번째 인수가 -2있었다면 프로세스를 한 번 더 반복했거나 -가능한 한 오랫동안 진행했을 것입니다.

각 인수주기마다 while루프가 한 번 처리됩니다. 각 루프의 맨 위에서 첫 번째 줄부터 stdin쉘 변수를 읽습니다 $l. 이것은 while head </dev/null; do :; done무한정 반복 되기 때문에 필요 head합니다. 파일 끝에 도달하면 리턴으로 표시합니다. EOF에 대한 확인을하기 위해 최선을 다하고 있습니다 있도록 read하고 printf기록합니다 $l에 플러스 바꿈을 stdout두 번째 인수는 양의 정수 경우에만.

read검사는 다른 루프가 호출 된 직후에 루프를 약간 복잡하게 만듭니다. 루프 는 상위 루프 의 각 반복 에 대해 표시된대로 forargs 2-$#$n반복 while합니다. 즉, 각 반복에 대해 첫 번째 arg는 명령 행에 지정된 값에서 하나씩 감소해야하지만 다른 모든 값은 원래 값을 유지해야하므로 $_n마커 var의 값을 각 값에서 빼야합니다. 첫 번째 인수의 값이 0보다 큽니다.

이는 함수의 주요 루프를 구성하지만 대부분의 코드는 맨 위에 있으며 함수를 파이프로 입력으로 깔끔하게 버퍼링 할 수 있도록 고안되었습니다. 이것은 먼저 backgrounded dd를 호출하여 블록 크기가 4k 인 출력에서 ​​tmpfile에 복사합니다. 그런 다음 함수는 홀드 루프를 설정합니다. 단일 루프에서도 거의 완료되지 않아야 dd합니다. 함수보다 먼저 파일에 단일 쓰기를 한 다음 stdin을 tmpfile에 연결된 파일 설명 자로 바꿉니다. 그 후 즉시 파일을 연결 해제합니다rm. 이것은 함수가 트랩을 요구하지 않고 또는 정리를 위해 스트림을 안정적으로 처리 할 수있게합니다. 함수가 fd에서 클레임을 해제하자마자 명명 된 파일 시스템 링크 만 이미 제거되었으므로 tmpfile의 존재가 중지됩니다.


답변

다음과 같이 bash 함수를 사용하십시오.

seq 1 30 > input.txt
f(){ head $1 input.txt | tail $2 >> output.txt ;}; f -11 -2; f -24 -3
cat output.txt
10
11
22
23
24

이 경우 약간 과잉이지만 필터가 커지면 이익이 될 수 있습니다.


답변