Lọc hoặc đặt các phần nhất định của tệp


14

Tôi có một tệp đầu vào với một số phần được phân định bằng thẻ bắt đầu và kết thúc, ví dụ:

line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D

Tôi muốn áp dụng một phép biến đổi cho tệp này sao cho các dòng X, Y, Z được lọc thông qua một số lệnh ( nlví dụ), nhưng các dòng còn lại đi qua không thay đổi. Lưu ý rằng nl(các dòng số) tích lũy trạng thái trên các dòng, vì vậy nó không phải là một phép biến đổi tĩnh đang được áp dụng cho từng dòng X, Y, Z. ( Chỉnh sửa : nó đã chỉ ra rằng nlcó thể làm việc trong một chế độ mà không yêu cầu tích lũy trạng thái, nhưng tôi chỉ sử dụng nlnhư một ví dụ để đơn giản hóa vấn đề Trong thực tế lệnh là một kịch bản tùy chỉnh phức tạp hơn.. Những gì tôi đang thực sự tìm kiếm cho là một giải pháp chung cho vấn đề áp dụng bộ lọc tiêu chuẩn cho phần phụ của tệp đầu vào )

Đầu ra sẽ giống như:

line A
line B
     1 line X
     2 line Y
     3 line Z
line C
line D

Có thể có một số phần như vậy trong tệp yêu cầu chuyển đổi.

Cập nhật 2 Ban đầu tôi không chỉ định điều gì sẽ xảy ra nếu có thêm một phần, ví dụ:

line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D
 @@inline-code-start
line L
line M
line N
@@inline-code-end

Kỳ vọng của tôi sẽ là trạng thái đó chỉ cần được duy trì trong một phần nhất định, đưa ra:

line A
line B
     1 line X
     2 line Y
     3 line Z
line C
line D
     1 line L
     2 line M
     3 line N

nhưng, tôi nghĩ việc giải thích vấn đề như yêu cầu trạng thái được giữ giữa các phần là hợp lệ và hữu ích trong nhiều ngữ cảnh.

Kết thúc cập nhật 2

Suy nghĩ đầu tiên của tôi là xây dựng một máy trạng thái đơn giản theo dõi phần chúng tôi đang ở:

#!/usr/bin/bash
while read line
do
  if [[ $line == @@inline-code-start* ]]
  then
    active=true
  elif [[ $line == @@inline-code-end* ]]
  then
    active=false
  elif [[ $active = true ]]
  then
    # pipe
  echo $line | nl
  else
    # output
    echo $line
  fi
done

Mà tôi chạy với:

cat test-inline-codify | ./inline-codify

Điều này không hoạt động vì mỗi cuộc gọi đến nllà độc lập, vì vậy số dòng không tăng:

line A
line B
     1  line X
     1  line Y
     1  line Z
line C
line D

Nỗ lực tiếp theo của tôi là sử dụng fifo:

#!/usr/bin/bash
mkfifo myfifo
nl < myfifo &
while read line
do
  if [[ $line == @@inline-code-start* ]]
  then
    active=true
  elif [[ $line == @@inline-code-end* ]]
  then
    active=false
  elif [[ $active = true ]]
  then
    # pipe
    echo $line > myfifo
  else
    # output
    echo $line
  fi
done
rm myfifo

Điều này cho đầu ra chính xác, nhưng theo thứ tự sai:

line A
line B
line C
line D
     1  line 1
     2  line 2
     3  line 3

Có lẽ có một số bộ nhớ đệm đang diễn ra.

Tôi đang đi về tất cả điều này sai? Đây có vẻ như là một vấn đề khá chung chung. Tôi cảm thấy như phải có một đường ống đơn giản sẽ giải quyết điều này.


nlkhông phải tích lũy trạng thái . Nhìn vào nl -dvà kiểm tra man/ infotrang của bạn để biết thông tin về dấu phân cáchnl của phần .
mikeerv

nl chỉ là một ví dụ. Trong trường hợp của tôi, tôi đang chạy một kịch bản tùy chỉnh chứ không phải nl.
James Scriven

Trong trường hợp đó, hãy làm rõ kịch bản của bạn đang làm gì.
terdon

Tôi đã làm rõ trong câu hỏi rằng tôi chỉ sử dụng nllàm bộ lọc ví dụ. Tôi nghĩ rằng nó sẽ đơn giản hóa câu hỏi bằng cách làm sáng tỏ các chi tiết về chính xác những gì bộ lọc đang làm, nhưng tôi có lẽ chỉ gây ra nhiều nhầm lẫn. Trong thực tế, tôi đang lọc phần phụ thông qua một công cụ tô sáng mã, cho một trình tạo blog tĩnh được phát triển tại nhà. Ngay bây giờ tôi đang sử dụng gnu source-highlight, nhưng điều đó có thể thay đổi và tôi có thể thêm nhiều bộ lọc hơn, chẳng hạn như một bộ định dạng.
James Scriven

Câu trả lời:


7

Tôi đồng ý với bạn - nó có thể một vấn đề chung chung. Một số tiện ích phổ biến có một số cơ sở để xử lý nó, mặc dù.


nl

nl, ví dụ, phân tách đầu vào thành các trang logic như được phân tách bằng dấu phân cách-d hai phần ký tự . Ba lần xuất hiện trên một dòng tất cả chỉ ra sự bắt đầu của một tiêu đề , hai cơ thể và một chân trang . Nó thay thế bất kỳ thứ nào được tìm thấy trong đầu vào bằng một dòng trống ở đầu ra - đó là những dòng trống duy nhất mà nó từng in

Tôi đã thay đổi ví dụ của bạn để bao gồm một phần khác và đưa nó vào ./infile. Vì vậy, nó trông như thế này:

line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D
@@start
line M
line N
line O
@@end

Sau đó, tôi chạy như sau:

sed 's/^@@.*start$/@@@@@@/
     s/^@@.*end$/@@/'  <infile |
nl -d@@ -ha -bn -w1

nlcó thể được yêu cầu tích lũy trạng thái trên các trang logic, nhưng nó không theo mặc định. Thay vào đó, nó sẽ đánh số các dòng đầu vào của nó theo kiểu và theo phần . Vì vậy, -hacó nghĩa là số tất cả các dòng tiêu đề-bncó nghĩa là không có dòng cơ thể - vì nó bắt đầu trong trạng thái cơ thể .

Cho đến khi tôi học tôi này sử dụng để sử dụng nlcho bất kỳ đầu vào, nhưng sau khi nhận ra rằng nlsản lượng có thể bóp méo theo mặc định của nó -delimiter \:Tôi đã học được để cẩn thận hơn với nó và bắt đầu sử dụng grep -nF ''cho đầu vào chưa được kiểm tra để thay thế. Nhưng một bài học khác được học ngày hôm đó là nlcó thể được áp dụng rất hữu ích ở các khía cạnh khác - chẳng hạn như điều này - nếu bạn chỉ sửa đổi đầu vào của nó một chút - như tôi đã làm vớised ở trên.

ĐẦU RA

  line A
  line B

1       line X
2       line Y
3       line Z

  line C
  line D

1       line M
2       line N
3       line O

Dưới đây là một số thông tin khác nl- bạn có để ý ở trên cách tất cả các dòng trừ những dòng được đánh số bắt đầu bằng dấu cách không? Khi nlcác dòng số, nó sẽ chèn một số ký tự nhất định vào đầu mỗi ký tự. Đối với những dòng đó, nó không phải là số - thậm chí là khoảng trắng - nó luôn khớp với thụt lề bằng cách chèn ( -wsố thứ tự + -skhoảng cách len) * khoảng trắng ở đầu các dòng không đánh số. Điều này cho phép bạn tái tạo chính xác nội dung không được đánh số bằng cách so sánh nó với nội dung được đánh số - và với rất ít nỗ lực. Khi bạn xem xét điều đó nlsẽ chia đầu vào của nó thành các phần hợp lý cho bạn và bạn có thể chèn các -sdấu tùy ý vào đầu mỗi dòng mà nó đánh số, thì việc xử lý đầu ra của nó khá dễ dàng:

sed 's/^@@.*start$/@@@@@@/
     s/^@@.*end/@@/; t
     s/^\(@@\)\{1,3\}$/& /' <infile |
nl -d@@ -ha -bn -s' do something with the next line!
'

Các bản in trên ...

                                        line A
                                        line B

 1 do something with the next line!
line X
 2 do something with the next line!
line Y
 3 do something with the next line!
line Z

                                        line C
                                        line D

 1 do something with the next line!
line M
 2 do something with the next line!
line N
 3 do something with the next line!
line O

GNU sed

Nếu nlkhông phải là ứng dụng đích của bạn, thì GNU sedcó thể execute một lệnh shell tùy ý cho bạn tùy thuộc vào một trận đấu.

sed '/^@@.*start$/!b
     s//nl <<\\@@/;:l;N
     s/\(\n@@\)[^\n]*end$/\1/
Tl;e'  <infile

Ở trên sedthu thập đầu vào trong không gian mẫu cho đến khi nó đủ để vượt qua thành công Test thay thế và ngừng btrang trại trở lại :label. Khi có, nó executes nlvới đầu vào được biểu diễn dưới dạng<< tài liệu ở đây cho tất cả phần còn lại của không gian mẫu.

Quy trình làm việc là như thế này:

  1. /^@@.*start$/!b
    • nếu một ^toàn bộ dòng $nào !không /phù hợp với /mô hình trên, sau đó nó được branched ra của kịch bản và autoprinted - vì vậy từ thời điểm này trở đi chúng tôi chỉ làm việc với một loạt các dòng bắt đầu với mô hình.
  2. s//nl <<\\@@/
    • trống s//trường /đứng cho địa chỉ cuối cùng sedcố gắng để phù hợp - vì vậy lệnh này thay thế toàn bộ @@.*startdòng cho nl <<\\@@thay thế.
  3. :l;N
    • Các :lệnh định nghĩa một nhãn chi nhánh - ở đây tôi thiết lập một tên :lAbel. Lệnh Next nối thêm dòng đầu vào tiếp theo vào không gian mẫu theo sau là \nký tự ewline. Đây là một trong một vài cách để có được một \newline trong một sedkhông gian mẫu - \nký tự ewline là một dấu phân cách chắc chắn cho một sedder người đã làm nó một lúc.
  4. s/\(\n@@\)[^\n]*end$/\1/
    • s///ubstlation này chỉ có thể thành công sau khi bắt đầu và chỉ trong lần xuất hiện đầu tiên sau của dòng kết thúc . Nó sẽ chỉ hoạt động trên một không gian mẫu trong đó \newline cuối cùng ngay lập tức được theo sau bằng cách @@.*endđánh dấu phần cuối $của không gian mẫu. Khi nó hoạt động, nó thay thế toàn bộ chuỗi khớp với nhóm \1đầu tiên , hoặc .\(\)\n@@
  5. Tl
    • các Tlệnh est ngành nhãn (nếu được cung cấp) nếu một thay thành công đã không xảy ra kể từ lần cuối cùng một dòng đầu vào đã được kéo vào không gian mô hình (như tôi làm w / N) . Điều này có nghĩa là mỗi khi một \newline được gắn vào không gian mẫu không khớp với dấu phân cách cuối của bạn, Tlệnh est sẽ thất bại và phân nhánh trở lại :label, dẫn đến sedkéo theo Ndòng ext và lặp cho đến khi thành công.
  6. e

    • Khi thay thế cho kết thúc trận đấu thành công và tập lệnh không phân nhánh trở lại cho một Test bị lỗi , sedsẽ ethực hiện một lệnh lgiống như thế này:

      nl <<\\@@\nline X\nline Y\nline Z\n@@$

Bạn có thể thấy điều này cho chính mình bằng cách chỉnh sửa dòng cuối cùng ở đó để trông như thế nào Tl;l;e.

Nó in:

line A
line B
     1  line X
     2  line Y
     3  line Z
line C
line D
     1  line M
     2  line N
     3  line O

while ... read

Một cách cuối cùng để làm điều này, và có thể là cách đơn giản nhất, là sử dụng một while readvòng lặp, nhưng vì lý do chính đáng. Vỏ - (đặc biệt là bashvỏ) - thường khá tuyệt vời khi xử lý đầu vào với số lượng lớn hoặc trong dòng ổn định. Điều này cũng có ý nghĩa - công việc của shell là xử lý ký tự đầu vào theo ký tự và gọi các lệnh khác có thể xử lý các nội dung lớn hơn.

Nhưng quan trọng về vai trò của nó là vỏ không được vượt read quá đầu vào - nó được chỉ định không đệm đầu vào hoặc đầu ra đến mức nó tiêu thụ quá nhiều hoặc không chuyển tiếp đủ thời gian mà các lệnh mà nó gọi bị thiếu - đến byte. Vì vậy, readlàm cho một bài kiểm tra đầu vào tuyệt vời - đểreturn biết thông tin về việc liệu còn đầu vào còn lại hay không và bạn nên gọi lệnh tiếp theo để đọc nó - nhưng nói chung nó không phải là cách tốt nhất để đi.

Tuy nhiên, đây là một ví dụ về cách người ta có thể sử dụng read các lệnh khác để xử lý đồng bộ hóa đầu vào:

while   IFS= read -r line        &&
case    $line in (@@*start) :;;  (*)
        printf %s\\n "$line"
        sed -un "/^@@.*start$/q;p";;
esac;do sed -un "/^@@.*end$/q;=;p" |
        paste -d: - -
done    <infile

Điều đầu tiên xảy ra cho mỗi lần lặp là readkéo theo một dòng. Nếu thành công, điều đó có nghĩa là vòng lặp chưa đạt EOF và do đó, trong vòng casekhớp với dấu phân cách bắt đầu , dokhối sẽ được thực thi ngay lập tức. Khác, printfin $linereadsedđược gọi.

sedsẽ giới thiệu pmọi dòng cho đến khi nó gặp điểm đánh dấu bắt đầu - khi nó qhoàn toàn nhập vào. Công -utắc nbuffered là cần thiết cho GNU sedvì nó có thể đệm khá khác, nhưng - theo thông số kỹ thuật - các POSIX khác sedsẽ hoạt động mà không cần xem xét đặc biệt - miễn <infilelà một tệp thông thường.

Khi sed quits đầu tiên , shell thực thi dokhối của vòng lặp - gọi một khối khác sedin mọi dòng cho đến khi nó gặp dấu hiệu kết thúc . Nó dẫn đầu ra của nó tới paste, bởi vì nó in số dòng trên mỗi dòng riêng của chúng. Như thế này:

1
line M
2
line N
3
line O

pastesau đó dán những thứ đó lại với nhau trên các :ký tự và toàn bộ đầu ra trông như sau:

line A
line B
1:line X
2:line Y
3:line Z
line C
line D
1:line M
2:line N
3:line O

Đây chỉ là ví dụ - mọi thứ có thể được thực hiện trong cả thử nghiệm hoặc thực hiện các khối ở đây, nhưng tiện ích đầu tiên không được tiêu thụ quá nhiều đầu vào.

Tất cả các tiện ích liên quan đều đọc cùng một đầu vào - và in kết quả của chúng - mỗi lượt trong lượt của chúng. Kiểu này mà có thể khó khăn để nhận được hang của - vì tiện ích khác nhau sẽ đệm hơn những người khác - nhưng bạn thường có thể dựa vào dd, headsedđể làm điều đúng (mặc dù, cho GNU sed, bạn cần cli-switch) và bạn phải luôn luôn có thể dựa vào read- bởi vì về bản chất, nó rất chậm . Và đó là lý do tại sao vòng lặp trên chỉ gọi nó một lần cho mỗi khối đầu vào.


Tôi đã thử nghiệm sedví dụ thứ hai mà bạn đưa ra, và nó hoạt động, nhưng tôi THỰC SỰ gặp khó khăn trong việc tìm kiếm cú pháp. (sed của tôi khá yếu và thường bị giới hạn ở s / findthis / replacethis / g. Tôi sẽ phải nỗ lực để ngồi xuống và thực sự hiểu về sed.)
James Scriven

@JamesScriven - Tôi chỉ chỉnh sửa để giải thích rõ hơn. Hãy cho tôi biết nếu nó không giúp đỡ. Tôi cũng đã thay đổi lệnh rất nhiều - bây giờ nó là những phần nhỏ hơn, hợp lý hơn.
mikeerv

4

Một khả năng là làm điều này với trình soạn thảo văn bản vim. Nó có thể ống các phần tùy ý thông qua các lệnh shell.

Một cách để làm điều này là bằng số dòng, sử dụng :4,6!nl. Lệnh ex này sẽ chạy nl trên các dòng bao gồm 4-6, đạt được những gì bạn muốn trên đầu vào ví dụ của bạn.

Một cách khác, tương tác nhiều hơn là chọn các dòng thích hợp bằng chế độ chọn dòng (shift-V) và các phím mũi tên hoặc tìm kiếm, sau đó sử dụng :!nl. Một chuỗi lệnh đầy đủ cho đầu vào ví dụ của bạn có thể là

/@@inline-code-start
jV/@@inline-code-end
k:!nl

Điều này không phù hợp với tự động hóa (câu trả lời sử dụng ví dụ sed là tốt hơn cho điều đó), nhưng đối với các chỉnh sửa một lần, nó rất hữu ích khi không phải dùng đến các bản sao 20 dòng.

Nếu bạn không quen với vi (m), ít nhất bạn nên biết rằng sau những thay đổi này, bạn có thể lưu tệp bằng cách sử dụng :wq.


Vâng, vim thật tuyệt vời! Nhưng tôi, trong trường hợp này, đang tìm kiếm một giải pháp kịch bản.
James Scriven

@JamesScriven, bất cứ ai nói vim đều không có kịch bản được xác định không đầy đủ. Trước tiên hãy tạo một thư mục dự án và trong thư mục đó sao chép tất cả các tệp khởi động của vim từ thư mục chính của bạn (ln -s hoạt động tốt ngoại trừ .vimrc mà chúng tôi sắp sửa đổi và .viminfo có thể bị nhiễu). Thêm định nghĩa hàm sẽ thực hiện công việc vào tệp .vimrc mới và sau đó gọi vim là HOME=$(pwd) vim -c 'call Mf()' f. Nếu bạn đang sử dụng xargs, bạn có thể muốn sử dụng gvim trên máy chủ xserver chuyên dụng để tránh làm hỏng tty của bạn (vnc là độc lập với thẻ video và có thể được theo dõi).
hildred

@hildred Hmmm ... Tôi không thể sử dụng [XSendEvent] ( tronche.com/gui/x/xlib/event-handling/XSendEvent.html ) để mô phỏng các lần nhấp chuột vào vim?
James Scriven

2

Cách khắc phục đơn giản nhất tôi có thể nghĩ đến là không sử dụng nlmà tự mình đếm các dòng:

#!/usr/bin/env bash
while read line
do
    if [[ $line == @@inline-code-start* ]]
    then
        active=true
    elif [[ $line == @@inline-code-end* ]]
    then
        active=false
    elif [[ $active = true ]]
    then
        ## Count the line number
        let num++;
        printf "\t%s %s\n" "$num" "$line"
    else
        # output
        printf "%s\n" "$line"
    fi
done

Sau đó, bạn chạy nó trên tập tin:

$ foo.sh < file
line A
line B
    1 line X
    2 line Y
    3 line Z
line C
line D

Cảm ơn terdon. Tôi đã cập nhật câu hỏi để làm rõ rằng tôi đang tìm kiếm một giải pháp chung để lọc phần phụ của đầu vào, thay vì ví dụ cụ thể về các dòng đánh số. có lẽ một lệnh ví dụ tốt hơn sẽ là "tac" (dòng ngược)
James Scriven

2

Nếu mục tiêu của bạn là gửi toàn bộ khối mã đến một cá thể quy trình thì bạn có thể tích lũy các dòng và trì hoãn đường ống cho đến khi bạn đạt đến cuối khối mã:

#!/bin/bash

acc=""

while read line
do
  if [[ $line == @@inline-code-start* ]]
  then
    active=true
    acc=""
  elif [[ $line == @@inline-code-end* ]]
  then
    active=false
    # Act on entire block of code
    echo "${acc:1}" | nl  # Chops off first leading new-line character using ${VAR:1}
  elif [[ $active = true ]]
  then
    acc=$( printf "%s\n%s" "$acc" "$line" )
  else
    # output
    echo $line
  fi
done

Điều này tạo ra các điều sau đây cho một tệp đầu vào lặp lại trường hợp thử nghiệm ba lần:

line A
line B
     1  line X
     2  line Y
     3  line Z
line C
line D
line A
line B
     1  line X
     2  line Y
     3  line Z
line C
line D
line A
line B
     1  line X
     2  line Y
     3  line Z
line C
line D

Để làm một cái gì đó khác với khối mã, ví dụ ngược lại và sau đó đánh số, chỉ cần đưa nó qua một cái gì đó khác: echo -E "${acc:1}" | tac | nl . Kết quả:

line A
line B
     1  line Z
     2  line Y
     3  line X
line C
line D

Hoặc đếm từ echo -E "${acc:1}" | wc:

line A
line B
      3       6      21
line C
line D

2

Chỉnh sửa đã thêm một tùy chọn để xác định bộ lọc do người dùng cung cấp

#!/usr/bin/perl -s
use IPC::Open2;
our $p;
$p = "nl" unless $p;    ## default filter

$/ = "\@\@inline-code-end\n";
while(<>) { 
   chomp;
   s/\@\@inline-code-start\n(.*)/pipeit($1,$p)/se;
   print;
}

sub pipeit{my($text,$pipe)=@_;
  open2(my $R, my $W,$pipe) || die("can open2");
  local $/ = undef;
  print $W $text;
  close $W;
  return <$R>;
}

Theo mặc định, bộ lọc là "nl". Để thay đổi tùy chọn bộ lọc, sử dụng "-p" với một số lệnh do người dùng cung cấp:

codify -p="wc" file

hoặc là

codify -p="sed -e 's@^@ ║ @; 1s@^@ ╓─\n@; \$s@\$@\n ╙─@'" file

Bộ lọc cuối cùng này sẽ xuất ra:

line A
line B
 ╓─
  line X
  line Y
  line Z
 ╙─
line C
line D

Cập nhật 1 Việc sử dụng IPC :: Open2 có vấn đề mở rộng: nếu vượt quá bộ đệm thì nó có thể bị chặn. (trong máy của tôi, đường ống đệm nếu 64K tương ứng với 10_000 x "dòng Y").

Nếu chúng ta cần những thứ lớn hơn (là chúng ta cần nhiều hơn 10000 "dòng Y"):

(1) cài đặt và sử dụng use Forks::Super 'open2';

(2) hoặc thay thế ống chức năng bằng cách:

sub pipeit{my($text,$pipe)=@_;
  open(F,">","/tmp/_$$");
  print F $text;
  close F;
  my $out = `$pipe < /tmp/_$$ `;
  unlink "/tmp/_$$";
  return $out;
}

Điều đó thật tuyệt. Tôi đoán các thủ thuật là bạn không xử lý từng dòng (bằng cách xác định lại $/scờ) và việc sử dụng ecờ để thực hiện cuộc gọi thực tế đến lệnh bên ngoài. Tôi thực sự thích ví dụ thứ hai (nghệ thuật ascii)!
James Scriven

Mặc dù vậy, tôi nhận thấy rằng điều này dường như không vượt quá vài nghìn dòng trong tiểu mục. Tôi nghi ngờ điều này có liên quan đến việc coi tiểu mục là một khối lớn của văn bản.
James Scriven

Cảm ơn. Có: `/ e` = eval; /s= ("." Có nghĩa là (.|\n)); $/xác định lại dấu phân cách đăng ký.
JJoao

@JamesScriven, bạn nói đúng (đường ống đang chặn). Hãy để tôi kiểm tra những gì đang diễn ra ...
JJoao

@JamesScriven, vui lòng xem cập nhật của tôi ...
JJoao

1

Đó là một công việc cho awk.

#!/usr/bin/awk -f
$0 == "@@inline-code-start" {pipe = 1; next}
$0 == "@@inline-code-end" {pipe = 0; close("nl"); next}
pipe {print | "nl"}
!pipe {print}

Khi tập lệnh nhìn thấy điểm đánh dấu bắt đầu, nó lưu ý rằng nó sẽ bắt đầu chuyển vào nl. Khi pipebiến là true (nonzero), đầu ra được dẫn vào nllệnh; khi biến là sai (unset hoặc zero), đầu ra được in trực tiếp. Lệnh piped được rẽ nhánh lần đầu tiên khi gặp cấu trúc đường ống cho mỗi chuỗi lệnh. Đánh giá sau đó của nhà điều hành đường ống với cùng một chuỗi sử dụng lại đường ống hiện có; một giá trị chuỗi khác nhau sẽ tạo ra một đường ống khác nhau. Các closechức năng đóng ống cho chuỗi lệnh nhất định.


Đây về cơ bản là logic tương tự như tập lệnh shell của bạn bằng cách sử dụng một ống có tên, nhưng dễ đánh vần hơn rất nhiều và logic đóng được thực hiện đúng. Bạn cần đóng đường ống đúng lúc, để thực hiện nllệnh thoát, xả bộ đệm của nó. Kịch bản của bạn thực sự đóng đường ống quá sớm: đường ống được đóng lại ngay khi lần đầu tiên echo $line >myfifokết thúc thực thi. Tuy nhiên, nllệnh chỉ nhìn thấy phần cuối của tệp nếu nó có một lát cắt thời gian trước khi tập lệnh thực thi tiếp theo echo $line >myfifo. Nếu bạn có một khối lượng dữ liệu lớn hoặc nếu bạn thêm sleep 1sau khi ghi vào myfifo, bạn sẽ thấy rằng nlchỉ xử lý dòng đầu tiên hoặc dòng nhanh đầu tiên, sau đó nó thoát vì nó đã thấy phần cuối của đầu vào.

Sử dụng cấu trúc của bạn, bạn cần giữ cho đường ống mở cho đến khi bạn không còn cần nó nữa. Bạn cần phải có một chuyển hướng đầu ra duy nhất vào đường ống.

nl <myfifo &
exec 3>&1
while IFS= read -r line
do
  if [[ $line == @@inline-code-start* ]]
  then
    exec >myfifo
  elif [[ $line == @@inline-code-end* ]]
  then
    exec >&3
  else
    printf '%s\n' "$line"
  fi
done

(Tôi cũng đã tận dụng cơ hội để thêm trích dẫn chính xác và như vậy - xem Tại sao tập lệnh shell của tôi bị nghẹt trên khoảng trắng hoặc các ký tự đặc biệt khác? )

Nếu bạn đang làm điều đó, bạn cũng có thể sử dụng một đường ống chứ không phải là một đường ống có tên.

while IFS= read -r line
do
  if [[ $line == @@inline-code-start* ]]
  then
    while IFS= read -r line && [[ $line != @@inline-code-end* ]] do
      printf '%s\n' "$line"
    done | nl
  else
    printf '%s\n' "$line"
  fi
done

giải pháp awk của bạn là thực sự tốt đẹp! Tôi nghĩ rằng đó là giải pháp ngắn gọn nhất (nhưng rất dễ đọc). Hành vi của awk về việc tái sử dụng đường ống đến nl có được đảm bảo hay không, hoặc có thể awk quyết định, "này, bây giờ bạn đã đủ đường rồi..Tôi sẽ đóng đường ống này và mở một đường ống mới"?. Giải pháp "đường ống" của bạn cũng thực sự tốt đẹp. Tôi đã giảm giá một cách tiếp cận với các vòng lặp được nhúng trong khi tôi nghĩ rằng nó có thể hơi khó hiểu, nhưng tôi nghĩ những gì bạn có là tuyệt vời. Có một dấu chấm phẩy bị mất trước do. (Tôi không có đại diện ở đây để thực hiện một chỉnh sửa nhỏ.)
James Scriven

1
... Tôi không thể làm cho giải pháp đường ống được đặt tên của bạn làm việc. Dường như có một điều kiện cuộc đua, như vậy phần đôi khi được chuyển sang nl đôi khi bị mất hoàn toàn. Ngoài ra, ff có phần thứ hai @@ inline-code-start / end, nó luôn bị mất.
James Scriven

0

OK, trước hết; Tôi hiểu rằng bạn không tìm cách đánh số các dòng trong các phần của tệp. Vì bạn chưa đưa ra một ví dụ thực tế về bộ lọc của bạn có thể là gì (ngoài nl), hãy giả sử rằng đó là bộ lọc

tr "[[:lower:]]" "[[:upper:]]"

tức là chuyển đổi văn bản sang tất cả chữ hoa; vì vậy, cho một đầu vào của

line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D

bạn muốn một đầu ra của

line A
line B
LINE X
LINE Y
LINE Z
line C
line D

Đây là xấp xỉ đầu tiên của tôi về một giải pháp:

#!/bin/sh
> file0
> file1
active=0
nl -ba "$@" | while IFS= read -r line
do
        case "$line" in
            ([\ 0-9][\ 0-9][\ 0-9][\ 0-9][\ 0-9][\ 0-9]"        @@inline-code-start")
                active=1
                ;;
            ([\ 0-9][\ 0-9][\ 0-9][\ 0-9][\ 0-9][\ 0-9]"        @@inline-code-end")
                active=0
                ;;
            (*)
                printf "%s\n" "$line" >> file$active
        esac
done
(cat file0; tr "[[:lower:]]" "[[:upper:]]" < file1) | sort | sed 's/^[ 0-9]\{6\}        //'

nơi không gian trước @@ chuỗi và gần cuối dòng cuối cùng là các tab. Xin lưu ý rằng tôi đang sử dụng nl cho mục đích riêng của tôi . (Tất nhiên tôi đang làm điều đó để giải quyết vấn đề của bạn , nhưng không cung cấp cho bạn đầu ra được đánh số dòng.)

Điều này đánh số các dòng của đầu vào để chúng ta có thể tách nó ra tại các điểm đánh dấu phần và biết làm thế nào để đặt nó lại với nhau sau này. Phần chính của vòng lặp dựa trên nỗ lực đầu tiên của bạn, có tính đến thực tế là các điểm đánh dấu phần có số dòng trên chúng. Nó chia đầu vào thành hai tệp: file0 (không hoạt động; không nằm trong một phần) và file1(hoạt động; trong một phần). Đây là những gì họ trông giống như cho đầu vào trên:

file0:
     1  line A
     2  line B
     8  line C
     9  line D

file1:
     4  line X
     5  line Y
     6  line Z

Sau đó, chúng tôi chạy file1(đó là sự kết hợp của tất cả các dòng trong phần) thông qua bộ lọc viết hoa; kết hợp điều đó với các dòng ngoài phần chưa được lọc; sắp xếp, để đưa chúng trở lại trật tự ban đầu của chúng; và sau đó loại bỏ các số dòng. Điều này tạo ra đầu ra được hiển thị gần đầu câu trả lời của tôi.

Điều này giả định rằng bộ lọc của bạn để lại số dòng một mình. Nếu nó không (ví dụ: nếu nó chèn hoặc xóa các ký tự ở đầu dòng), thì tôi tin rằng, cách tiếp cận chung này vẫn có thể được sử dụng, nhưng sẽ yêu cầu một số mã hóa phức tạp hơn một chút.


nlđã thực hiện hầu hết các công việc ở đó - đó là những gì -dtùy chọn loại bỏ của nó là dành cho.
mikeerv

0

Một tập lệnh shell sử dụng sed để xuất ra các đoạn không phân chia ranh giới và cung cấp các đoạn được phân chia ranh giới vào một chương trình lọc:

#!/bin/bash

usage(){
    echo "  usage: $0 <input file>"
}

# Check input file
if [ ! -f "$1" ]; then
    usage
    exit 1
fi

# Program to use for filtering
# e.g. FILTER='tr X -'
FILTER='./filter.sh'

# Generate arrays with starting/ending line numbers of demarcators
startposs=($(grep -n '^@@inline-code-start$' "$1" | cut -d: -f1))
endposs=($(grep -n '^@@inline-code-end$' "$1" | cut -d: -f1))

nums=${#startposs[*]}
nume=${#endposs[*]}

# Verify both line number arrays have the same number of elements
if (($nums != $nume)); then
    echo "Tag mismatch"
    exit 2
fi

lastline=1
i=0
while ((i < nums)); do
    # Exclude lines with code demarcators
    sprev=$((${startposs[$i]} - 1))
    snext=$((${startposs[$i]} + 1))
    eprev=$((${endposs[$i]} - 1))

    # Don't run this bit if the first demarcator is on the first line
    if ((sprev > 1)); then
        # Output lines leading up to start demarcator
        sed -n "${lastline},${sprev} p" "$1"
    fi

    # Filter lines between demarcators
    sed -n "${snext},${eprev} p" "$1" | $FILTER

    lastline=$((${endposs[$i]} + 1))
    let i++
done

# Output lines (if any) following last demarcator
sed -n "${lastline},$ p" "$1"

Tôi đã viết tập lệnh này vào một tập tin có tên detagger.sh và sử dụng nó như vậy : ./detagger.sh infile.txt. Tôi đã tạo một tệp filter.sh riêng để bắt chước chức năng lọc trong câu hỏi:

#!/bin/bash
awk '{ print "\t" NR " " $0}'

Nhưng hoạt động lọc có thể được thay đổi trong mã.

Tôi đã cố gắng làm theo ý tưởng về một giải pháp chung chung với điều này để các hoạt động như các dòng đánh số không yêu cầu đếm bổ sung / nội bộ. Kịch bản kiểm tra một cách thô sơ để thấy rằng các thẻ phân định là theo cặp và hoàn toàn không xử lý các thẻ lồng nhau một cách duyên dáng.


-1

Cảm ơn vì tất cả những ý tưởng tuyệt vời. Tôi đã đưa ra giải pháp của riêng mình bằng cách theo dõi phần phụ trong tệp tạm thời và chuyển tất cả nó cùng một lúc đến lệnh bên ngoài của tôi. Điều này rất giống với những gì Supr đề xuất (nhưng với biến shell thay vì tệp tạm thời). Ngoài ra, tôi thực sự thích ý tưởng sử dụng sed, nhưng cú pháp cho trường hợp này có vẻ hơi quá so với tôi.

Giải pháp của tôi:

(Tôi nlchỉ sử dụng như một bộ lọc ví dụ)

#!/usr/bin/bash

while read line
do
  if [[ $line == @@inline-code-start* ]]
  then
    active=true
    tmpfile=$(mktemp)
    trap "rm -f $tmpfile" EXIT
  elif [[ $line == @@inline-code-end* ]]
  then
    active=false
    <$tmpfile nl
    rm $tmpfile
  elif [[ $active = true ]]
  then
    echo $line >> $tmpfile
  else
    echo $line
  fi
done

Tôi không muốn phải xử lý các tệp tạm thời, nhưng tôi hiểu rằng các biến shell có thể có giới hạn kích thước khá thấp và tôi không biết bất kỳ cấu trúc bash nào hoạt động như tệp tạm thời, nhưng sẽ tự động biến mất khi quá trình kết thúc


Tôi nghĩ bạn muốn để có thể “trạng thái tích lũy trên đường”, do đó, ví dụ, sử dụng dữ liệu thử nghiệm của mike, dây chuyền M, NOsẽ được đánh số 4, 56. Điều này không làm điều đó. Câu trả lời của tôi là (ngoài thực tế là, trong phiên bản hiện tại của nó, nó không hoạt động nlnhư một bộ lọc). Nếu đây câu trả lời được đưa ra cho bạn những kết quả mà bạn muốn, sau đó những gì cậu có nghĩa là bởi “tình trạng tích lũy trên đường”? Ý của bạn là bạn chỉ muốn duy trì trạng thái qua từng phần chứ không phải giữa các phần (trên)? (Tại sao bạn không đặt một ví dụ đa phần vào câu hỏi của mình?)
Scott

@ Hủy bỏ - sử dụng nl -pđể có được M,N,O==4,5,6.
mikeerv

Tôi đã cập nhật câu hỏi để làm rõ rằng tôi chỉ quan tâm đến việc duy trì trạng thái trong tiểu mục, mặc dù tôi nghĩ cách giải thích khác cũng thú vị không kém.
James Scriven
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.