Nối các dòng văn bản với bắt đầu lặp lại


7

Tôi có một tệp văn bản dài (một tệp tab cho trình soạn thảo stardict) bao gồm các dòng theo định dạng sau:

word1  some text
word1  some other text
word2  more text
word3  even more

và muốn chuyển đổi nó thành

word1  some text<br>some other text
word2  more text
word3  even more

Điều này có nghĩa là các dòng tiếp theo (tệp được sắp xếp) bắt đầu bằng cùng một từ nên được hợp nhất với một từ duy nhất (ở đây các định nghĩa được phân tách bằng <br>). Các dòng có khởi đầu bằng nhau cũng có thể xuất hiện thường xuyên hơn chỉ hai lần. Ký tự phân tách từ và định nghĩa là ký tự tab và là duy nhất trên mỗi dòng. word1, word2, word3Là của placeholders khóa học cho một cái gì đó tùy ý (trừ tab và ký tự xuống dòng) mà tôi không biết trước.

Tôi có thể nghĩ về một đoạn mã Perl dài hơn để thực hiện điều này, nhưng tự hỏi liệu có một giải pháp ngắn nào trong Perl hoặc một cái gì đó cho dòng lệnh. Có ý kiến ​​gì không?

Câu trả lời:


3

Đây là quy trình chuẩn cho awk

awk '
{
  k=$2
  for (i=3;i<=NF;i++)
    k=k " " $i
  if (! a[$1])
    a[$1]=k
  else
    a[$1]=a[$1] "<br>" k
}
END{
  for (i in a)
    print i "\t" a[i]
}' long.text.file

Nếu tập tin được sắp xếp theo từ đầu tiên trong dòng thì tập lệnh có thể đơn giản hơn

awk '
{
  if($1==k)
    printf("%s","<br>")
  else {
    if(NR!=1)
      print ""
    printf("%s\t",$1)
  }
  for(i=2;i<NF;i++)
    printf("%s ",$i)
  printf("%s",$NF)
  k=$1
}
END{
print ""
}' long.text.file

Hoặc chỉ bash

unset n
while read -r word definition
do
    if [ "$last" = "$word" ]
    then
        printf "<br>%s" "$definition"
    else 
        if [ "$n" ]
        then
            echo
        else
            n=1
        fi
        printf "%s\t%s" "$word" "$definition"
        last="$word"
     fi
done < long.text.file
echo

Có vẻ tốt! Chỉ khi tôi chạy nó, đầu ra không chứa bất kỳ ký tự tab nào. Cần có một từ giữa mỗi từ và định nghĩa của nó.
highsciguy

@highsciguy Có cả hai tập lệnh được chỉnh sửa.
Costas

Costas, mã của bạn thay đổi dữ liệu; không chỉ TAB (như đã đề cập trong một bình luận trước đó) mà còn cả các chuỗi không gian. Đây có thể là hành vi không mong muốn.
Janis

3
perl -p0E 'while(s/^((.+?)\t.*)\n\2\t/$1<br>/gm){}' 

(Phải mất 2 giây để xử lý từ điển 23 MB, 1,5Mlines, trong máy tính xách tay cũ 6 năm của tôi)


Tôi có thể xác nhận điều này nhanh hơn nhiều so với giải pháp sed . Đối với một tệp, nó giảm thời gian thực hiện từ khoảng 8 phút xuống dưới một giây.
pcworld

3

Với sed:

sed '$!N;/^\([^\t]*\t\)\(.*\)\(\n\)\1/!P;s//\3\1\2<br>/;D' <<\IN
word1  some text
word1  some other text
word1  some other other text
word2  more text
word3  even more
word3  and still more
IN

(lưu ý: với nhiều seds, \tlối thoát trên không hợp lệ và <tab>nên sử dụng ký tự chữ

Và nếu bạn có GNU, sedbạn có thể viết nó dễ dàng hơn một chút:

sed -E '$!N;/^(\S+\t)(.*)\n\1/!P;s//\n\1\2<br>/;D' <infile

Nó hoạt động bằng cách xếp chồng dần dần đầu vào khi nó được đọc. Nếu hai dòng liên tiếp không bắt đầu bằng cùng một chuỗi không gian, thì dòng đầu tiên trong số này được tô màu P. Khác dòng mới can thiệp được di chuyển đến đầu của dòng và chuỗi phù hợp ngay sau nó (để bao gồm các tab) được thay thế w / chuỗi <br>.

Lưu ý rằng phương pháp xếp chồng được sử dụng ở đây có thể có ý nghĩa về hiệu suất nếu dòng sedlắp ráp phát triển rất dài. Nếu nó phát triển dài hơn 8kb thì nó sẽ vượt quá kích thước bộ đệm không gian mẫu tối thiểu được chỉ định bởi POSIX.

Bất kể khả năng nào trong hai khả năng xảy ra, cuối cùng của tất cả các sed Delip cho đến \nký tự ewline xuất hiện đầu tiên trong không gian mẫu và bắt đầu lại với những gì còn lại. Và do đó, khi hai dòng liên tiếp không bắt đầu bằng các chuỗi giống hệt nhau thì chuỗi đầu tiên được in và xóa, thay vào đó, việc thay thế được thực hiện và Delete chỉ xóa \newline trước đó đã tách chúng.

Và vì vậy, lệnh trên in:

word1  some text<br>some other text<br>some other other text
word2  more text
word3  even more<br>and still more

Tôi đã sử dụng một <<\HERE_DOCđầu vào ở trên, nhưng có lẽ bạn nên bỏ mọi thứ từ <<\INtrên và sử dụng </path/to/infilethay thế.


Xin lỗi, tùy chọn `sed -E 'là gì?
JJoao

2
@JJoao - xin lỗi vì điều gì? Các -Etùy chọn để GNU sedlà một sự thay thế không có giấy tờ để sử dụng -r, ngoại trừ việc 1. nó có ý nghĩa hơn (những gì đã -rbao giờ có nghĩa gì không?) , 2. Nó cũng hoạt động trong BSD sed, 3. POSIX có một sự thay đổi lịch trình dự kiến sẽ được áp dụng cho phiên bản tiếp theo của thông số kỹ thuật chính thức ban phước -Enhư cú pháp chính xác để cho phép các biểu thức chính quy mở rộng trong a sed.
mikeerv

2

Đây thực sự là tiêu chuẩn cho awk. Đây là một giải pháp ngắn gọn không thay đổi dữ liệu vận hành:

awk 'BEGIN { FS="\t" }
     $1!=key { if (key!="") print out ; key=$1 ; out=$0 ; next }
     { out=out"<br>"$2 }
     END { print out }'

Nó là. Vì OP nói rằng có một TAB duy nhất ở giữa "từ" và phần còn lại của dữ liệu. Tâm FSđịnh nghĩa!
Janis

Không. Tôi gán toàn bộ $0(có chứa TAB) cho out.
Janis

Bây giờ hai bình luận của tôi trông hơi lạ bởi vì người mà tôi đang trả lời đã xóa bình luận của anh ấy. Để tóm tắt; điều cốt lõi là giải pháp được trình bày xem xét tất cả các yêu cầu (bao gồm giữ nguyên dữ liệu và phân tách TAB). - Mà nói; Tôi tự hỏi tại sao có (ngoài upvote) cũng là một downvote. Cử tri xin vui lòng thêm một lý do cho điều đó.
Janis

Có vẻ là ngắn nhất, nhưng vì một số lý do, nó thất bại trên nhiều dòng. Có lẽ bởi vì tập tin chứa rất nhiều ký tự đặc biệt utf-8?
highsciguy

Với một GNU GNU không quá cũ, bạn đã hỗ trợ UTF-8. Là địa phương của bạn được thiết lập phù hợp? (Một cái gì đó như LC_ALL=en_US.UTF-8.) Nếu không, sẽ rất hữu ích khi lấy một số dòng mẫu mà bạn thấy có vấn đề; nó cũng có thể là định dạng dữ liệu không phải ở mọi nơi như bạn mong đợi. Phản hồi của bạn được đánh giá cao, để theo dõi nơi xử lý vấn đề nằm ở đâu; một nơi nào đó sẽ được sửa chữa
Janis

1

Trong trăn:

import sys

def join(file_name, join_text):
    prefix = None
    current_line = ''
    for line in open(file_name):
        if line and line[-1] == '\n':
            line = line[:-1]
        try:
            first_word, rest = line.split('\t', 1)
        except:
            first_word = None  # empty line or one without tab
            rest = line
        if first_word == prefix:
            current_line += join_text + rest
        else:
            if current_line:
                print current_line
            current_line = line
            prefix = first_word

    if current_line:  # do the last line(s)
        print current_line


join(sys.argv[2], sys.argv[1])

Điều này hy vọng dấu phân cách ( <br>) là đối số đầu tiên cho chương trình và tên tệp là đối số thứ hai


-1

thử

awk 'BEGIN { before="" } 
{ if ( $1 == before ) { $1="" ; printf "<br>%s",$0 ; } 
  else { printf "\n%s",$0 ;} ; before=$1 ; } 
END { printf "\n"  ;}'

cung cấp cho đầu vào của bạn

word1  some text<br> some other text
word2  more text
word3  even more

tha awk về cơ bản nhớ từ đầu tiên trên dòng trước và không in dòng mới.

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.