head
/는tail
거의 전체 파일을 반복해야 합니다(인수로 제공하는 줄의 위치에 따라 다름). 그런 다음 결과를 새 파일에 복사하고 이전 파일을 삭제합니다.전체 파일에 대해 반복 되는지는 확실하지 않지만
sed
해당 결과를 새 파일에 복사하고 이전 파일을 삭제해야 합니다.-i
(그 자리에서) 백그라운드에 임시 파일을 생성하므로 동일하게 적용됩니다 .
포인터를 파일의 첫 번째 줄로 이동하여 원하는 줄로 이동하면 어떨까요?
우리는 어떻게 그런 일을 할 수 있습니까? 꼭 C로 해야 하나요? 다른 방법이 있나요?
말이 돼? ? 내가 잘못? 그렇다면 왜?
답변1
포인터를 파일의 첫 번째 줄로 이동하여 원하는 줄로 이동하면 어떨까요?
"파일의 첫 번째 줄에 대한 포인터"와 같은 것이 없기 때문입니다.
파일 수정의 기본 작업은 특정 범위의 바이트 포함(예: 일부를 동일한 길이의 데이터로 대체), 추가(즉, 끝에 추가), 자르기(즉, 끝에서 삭제)입니다.
대부분의 파일 시스템은 파일을 고정 크기 블록에 저장하지만 마지막 블록은 부분적일 수 있습니다. 수정으로 인해 수정 중인 항목의 크기가 변경되는 경우 변경이 끝나지 않거나 수정으로 인해 데이터가 정수 블록 수만큼 이동하지 않는 한 데이터를 제자리에서 수정할 수 없습니다. 전체 블록 수에 걸쳐 데이터를 이동하는 것은 우연의 문제일 뿐이며 이를 수행할 수 있는 광범위한 인터페이스는 없습니다.
파일 시작 부분의 데이터를 삭제하는 가장 효율적인 방법은 보관하려는 데이터를 새 파일에 복사하는 것입니다. 이것이 바로 "무엇을 해야 하는가" tail -n +42
또는 " sed '41,$p'
무엇을 해야 하는가"입니다.
1최신 Linux 시스템에는 파일의 일부를 삭제하는 시스템 호출이 있습니다.fallocate(fd, FALLOC_FL_COLLAPSE_RANGE, …)
, 유틸리티를 통해 호출할 수 있습니다.fallocate --collapse-range=…
. 그리고 FALLOC_FL_INSERT_RANGE
.--insert-range
그러나 블록으로 제한되어 텍스트 파일에는 거의 쓸모가 없으며 모든 파일 시스템에서 작동하지 않습니다.
답변2
Giles는 나보다 한 발 앞서 있었습니다. "파일의 첫 번째 줄에 대한 포인터"가 없습니다. 파일의 첫 번째 줄(파일의 시작 부분)은 항상 파일의 첫 번째 문자입니다. (이런 개념을 인식하는 모호하고 별도의 애플리케이션이 있을 수 있지만 시스템 수준에서는 그런 것이 없습니다.)
당신이 이미 알고 있는 것:
다음과 같은 명령
sed '1,6d' filename
sed -n '7,$p' filename
tail -n +7 filename
(다른 변형이 있을 수 있음) 처음 6개 행을 제외한 모든 행을 작성합니다.filename
표준 출력으로. (물론 그들은 모두 문서를 다 읽었습니다.) 이렇게 하면,
sed -n '1,6p' filename
sed '7,$d' filename
head -n 6 filename
sed '6q' filename
처음 6줄을 씁니다.filename
표준 출력으로. 처음 두 개는 전체 파일을 읽을 수도 있고 그렇지 않을 수도 있습니다.
반품,
명령 input_file 이름>같은 파일 이름에서 설명한 대로 작동하지 않습니다.">"에 대한 경고.
당신이 모를 수도 있는 것은 다음과 같습니다:
명령 매개변수 1<>파일 이름
열 예정이다filename
읽기와 쓰기를 위해
자르지 않고(쪼개지 않고). 그래서,
sed '1,6d'파일 이름 1<>같은 파일 이름당신이 찾고 있는 솔루션을 향한 첫 번째 단계일 수도 있습니다. 이는 아마도 삭제하려는 첫 번째 항목과 비슷할 것입니다.중간 사이즈파일의 줄은 "제자리에" 있습니다. 다른 파일을 만들지 않고도 파일을 읽고 동시에 덮어씁니다. 만약에중간 사이즈충분히 작은 경우(또는 특히 첫 번째 바이트 수가중간 사이즈줄이 충분히 작음), 이는 파일의 각 블록을 한 번 읽고 각 블록을 한 번 쓸 수 있으며 이보다 더 나은 방법은 없습니다.
오직첫 번째단계?
이 테스트 파일을 만들었습니다.
$ 고양이 -n foo 1 2야드 3 에게지 4 jklmnop 5qrstuvwxy 6 z0123456789 7ABCDEFGHIJKLM 8 옛날 어느 음산한 자정에 내가 힘이 없고 피곤하여 생각에 잠겼더니 9 기이하고 호기심이 많아 잊혀진 많은 지식 중에서— 10 내가 졸려고 고개를 끄덕이는데 갑자기 두드리는 소리가 나서 11. 누군가 내 집 문을 살짝 두드리는 것 같다. 12 "손님이 내 집 문을 두드렸다." 내가 속삭였다. 13. 바로 그거야, 그거야. " 14 퀵 브라운 폭스 점프 15회 16. 게으른 개. 옛날 옛적에 17. 이 지루한 한밤중에
파일은 라인 길이(개행 포함)가 다음과 같도록 주의 깊게 구성되었습니다.2, 4, 6, 8, 10, 12, 14, 63, 57, 63, 58, 62, 63,16, 18, 20, 그리고번호 22. 따라서 처음 6개 줄에는 2+4+6+8+10+12=42바이트가 포함됩니다. 마지막 두 줄에는 20+22바이트가 포함되어 있는데, 공교롭게도(!) 역시 42바이트입니다. (총 파일 크기는 504 입니다.) 그래서,
$ ls -l foo -rw-r--r-- 1내 사용자 이름 내 그룹 이름504 5월 18일 04:25 리치 $ sed '1,6d' foo 1<> foo $ ls -l foo -rw-r--r-- 1내 사용자 이름 내 그룹 이름504 5월 18일 04:32 부자 $ 고양이 -n foo 1ABCDEFGHIJKLM 2 옛날 어느 음산한 자정에 내가 힘이 없고 피곤하여 생각하고 있을 때 3 기이하고 호기심이 많아 잊혀진 많은 지식 중에서— 4 내가 고개를 끄덕이다가 졸려고 하는데 갑자기 두드리는 소리가 나서 5 그것은 내 방문을 부드럽게 두드리는 것과 같습니다. 6 “손님이 오십니다.” 나는 속삭였습니다. “내 문을 두드리는데. 7그게 다예요, 그게 다예요. " 8 퀵 브라운 9번의 여우 점프 10. 게으른 개. 옛날 옛적에 11. 이 지루한 한밤중에 12 게으른 개. 옛날 옛적에 13. 이 한밤중은 지루하다.
알았어, 알았어. 처음 여섯 줄은 사라졌어. 원래 라인 7("ABCDEFGHIJKLM")은 이제 라인 1입니다. 그런데 이게 뭐죠? 파일이 17줄에서 13줄로 변경되었습니다. 11(17−6)이어야 합니다. 마지막 두 줄("게으른 개... 한밤중의 둔함")이 두 번 등장합니다.
이것은 연산자의 함정 중 하나입니다 1<>
. 출력 파일을 자르지 않으면 시작한 파일보다 작지 않은 파일로 끝나게 됩니다. 구체적으로 여기서의 출력은 sed '1,6d' foo
462바이트(처음 6줄에 42바이트가 포함되어 있으므로 504-42)이므로 출력 파일의 처음 462바이트를 덮어씁니다. 이는 마지막 42 foo
바이트 foo
외에 처음 462바이트이기도 합니다. 바이트 (504−462) - 마지막 두 줄을 덮어쓰지 않습니다. 마지막 두 줄의 복사본 두 개("Lazy dog... 한밤중의 둔함")는 의 출력이고 sed
그 뒤에는 파일의 원래 내용이 남아 있습니다.
그럼 다음은 무엇입니까?
이제 우리가 해야 할 일은 파일의 마지막 42바이트를 삭제하는 것뿐입니다. 공교롭게도 이할 수 있는포인터를 파일 끝으로 이동하면 됩니다. 글쎄, 그것은 실제로 포인터가 아닙니다. 정수 파일 크기(potAto, potAHto)입니다. 지난 20~30년 동안 Unix에서는 파일을 원하는 크기로 자르고 해당 지점 이전의 데이터를 그대로 유지하며 해당 지점 이후의 데이터를 삭제할 수 있었습니다.
이를 수행할 수 있는 고대 명령은 다음과 같습니다.
dd if=/dev/null bs=462 seek=1 of=foo 2> /dev/null
462바이트부터 복사합니다 /dev/null
. foo
네, 좀 혼란스럽습니다. 이 기능을 수행하는 새로운 명령은 다음과 같습니다.
truncate -s 462 foo
이는 모든 시스템에 존재하지 않을 수 있습니다. POSIX에서는 이를 지정하지 않습니다.
그래서 이 모든 것을 종합해보면,
#!/bin/sh
filename="$1"
bytes_to_remove=$(sed '6q' "$filename" | wc -c)
total_size=$(stat -c '%s' "$filename")
sed '1,6d' "$filename" 1<> "$filename"
new_size=$((total_size - bytes_to_remove))
truncate -s "$new_size" "$filename"
wc -c
생성된 처음 6줄의 문자 수를 계산 하고 sed '6q'
이를 전체 파일 크기에서 뺀 다음 파일을 해당 크기로 자릅니다. 대체 명령을 사용하여 첫 번째 명령을 출력할 수 있습니다.중간 사이즈행 또는 마지막 행N~M줄의 마지막 줄을 다음으로 바꿀 수 있습니다.
dd if=/dev/null bs="$new_size" seek=1 of="$filename" 2> /dev/null
지침:
나는 이것을 파일에서 테스트하지 않았습니다
- CR-LF 줄 끝, 또는
- 멀티바이트 문자,
이는 문제가 될 수 있습니다.
답변3
보고 있다꼬리의 유래, 그렇습니다아니요실제로 전체 파일을 반복하는 것 같습니다. 끝에서 시작하여 올바른 개행 수(종료되지 않는 행의 초과분 포함)가 나타날 때까지 뒤로 읽고 해당 위치를 기록한 다음 건너뜁니다.도착하다해당 위치를 선택한 다음 파일(또는 파이프 또는 입력 데이터)을 덤프합니다.