Thao tác văn bản với sed


12

Hiện tại, tôi có nhiều tệp văn bản có nội dung trông như thế này (có nhiều dòng):

565 0 10 12 23 18 17 25
564 1 7 12 13 16 18 40 29 15

Tôi muốn thay đổi từng dòng để có định dạng sau:

0 565:10:1 565:12:1 565:23:1 565:18:1 565:17:1 565:25:1
1 564:7:1 564:12:1 564:13:1 564:16:1 564:18:1 564:40:1 564:29:1 564:15:1

Có cách nào để làm điều trên bằng cách sử dụng sed? Hay tôi cần phải dùng đến Python?

Câu trả lời:


22

Bạn có thể làm điều đó với sed, vâng, nhưng các công cụ khác đơn giản hơn. Ví dụ:

$ awk '{
        printf "%s ", $2; 
        for(i=3;i<=NF;i++){
            printf "%s:%s:1 ",$1,$(i) 
        }
        print ""
       }' file 
0 565:10:1 565:12:1 565:23:1 565:18:1 565:17:1 565:25:1 
1 564:7:1 564:12:1 564:13:1 564:16:1 564:18:1 564:40:1 564:29:1 564:15:1 

Giải trình

awk sẽ chia mỗi dòng đầu vào khoảng trắng (theo mặc định), tiết kiệm từng lĩnh vực như $1, $2, $N. Vì thế:

  • printf "%s ", $2; sẽ in trường thứ 2 và dấu cách.
  • for(i=3;i<=NF;i++){ printf "%s:%s:1 ",$1,$(i) }: sẽ lặp lại các trường 3 đến trường cuối cùng ( NFlà số lượng các trường) và với mỗi trường, nó sẽ in trường thứ 1, a :, sau đó là trường hiện tại và a :1.
  • print "" : điều này chỉ in một dòng mới cuối cùng.

Hoặc Perl:

$ perl -ane 'print "$F[1] "; print "$F[0]:$_:1 " for @F[2..$#F]; print "\n"' file 
0 565:10:1 565:12:1 565:23:1 565:18:1 565:17:1 565:25:1 
1 564:7:1 564:12:1 564:13:1 564:16:1 564:18:1 564:40:1 564:29:1 564:15:1 

Giải trình

-alàm cho perlhành vi như awkvà phân chia đầu vào của nó trên khoảng trắng. Ở đây, các trường được lưu trữ trong mảng @F, có nghĩa là trường thứ 1 sẽ là $F[0], thứ 2, $F[1]v.v.

  • print "$F[1] " : in trường thứ 2.
  • print "$F[0]:$_:1 " for @F[2..$#F];: lặp qua các trường 3 đến trường cuối cùng ( $#Flà số phần tử trong mảng @F, do đó, @F[2..$#F]lấy một lát mảng bắt đầu từ phần tử thứ 3 cho đến hết mảng) và in trường thứ 1, a :, sau đó là trường hiện tại và a :1.
  • print "\n" : điều này chỉ in một dòng mới cuối cùng.

12

Đây là kinh khủng sed đường!

$ sed -r 's/^([0-9]+) ([0-9]+) ([0-9]+)/\2 \1:\3:1/; :a s/([0-9]+)(:[0-9]+:1) ([0-9]+)( |$)/\1\2 \1:\3:1 /; t a; s/ $//' file
0 565:10:1 565:12:1 565:23:1 565:18:1 565:17:1 565:25:1 
1 564:7:1 564:12:1 564:13:1 564:16:1 564:18:1 564:40:1 564:29:1 564:15:1

Dễ đọc hơn:

sed -r '
s/^([0-9]+) ([0-9]+) ([0-9]+)/\2 \1:\3:1/
:a 
s/([0-9]+)(:[0-9]+:1) ([0-9]+)( |$)/\1\2 \1:\3:1 /
t a
s/ $//'

Ghi chú

  • -r sử dụng ERE
  • s/old/new/thay thế oldbằngnew
  • ^([0-9]+) lưu một số số ở đầu dòng
  • \1 phản hồi cho mẫu lưu đầu tiên
  • :a gắn nhãn phần này của kịch bản a
  • ( |$) hoặc một khoảng trắng hoặc cuối dòng
  • t kiểm tra xem lần thay thế cuối cùng có thành công hay không - nếu có, hãy thực hiện lệnh tiếp theo
  • atìm nhãn :avà làm lại
  • s/ $// xóa dấu cách

Vì vậy, sau khi thêm cấu trúc vào phần đầu tiên, chúng tôi liên tục tìm thấy phiên bản cuối cùng của cấu trúc và áp dụng nó cho số tiếp theo ...

Nhưng tôi đồng ý các công cụ khác làm cho nó dễ dàng hơn ...


Tôi đã chờ đợi giải pháp sed của bạn: D
Ravexina

: D phải mất một lúc @Ravexina - Tôi nghĩ muru có thể làm cho sạch hơn
Zanna

5

Với awk:

awk '{printf "%s ",$2; for (i=3; i<=NF; i++) printf $1":"$i":1 "; printf "\n"}' file

hoặc với bash:

while read -r -a a; do                  # read line to array a
  printf "%s " ${a[1]}                  # print column #1
  for ((i=2;i<${#a[@]};i++)); do        # loop from column #2 to number of columns
    printf "%s " "${a[0]}:${a[$i]}:1"   # print content/values
  done
  echo                                  # print line break
done < file                             # read file from stdin

Đầu ra:

0 565: 10: 1 565: 12: 1 565: 23: 1 565: 18: 1 565: 17: 1 565: 25: 1 
1 564: 7: 1 564: 12: 1 564: 13: 1 564: 16: 1 564: 18: 1 564: 40: 1 564: 29: 1 564: 15: 1: 

5

Vâng, bạn có thể làm điều đó trong sed, nhưng python cũng hoạt động.

$ ./reformatfile.py  input.txt                                                                        
0 565:10:1 565:12:1 565:23:1 565:18:1 565:17:1 565:25:1
1 564:7:1 564:12:1 564:13:1 564:16:1 564:18:1 564:40:1 564:29:1 564:15:1

Nội dung của reformatfile.pylà như vậy:

#!/usr/bin/env python3
import sys

with open(sys.argv[1]) as fd:
    for line in fd:
        words = line.strip().split()
        pref = words[0]
        print(words[1],end=" ")
        new_words = [ ":".join([pref,i,"1"]) for i in words[2:] ]
        print(" ".join(new_words))

Cái này hoạt động ra sao? Thực sự không có gì đặc biệt đang diễn ra. Chúng tôi mở đối số dòng lệnh đầu tiên dưới dạng tệp để đọc và tiến hành chia nhỏ từng dòng thành "từ" hoặc các mục riêng lẻ. Các từ đầu tiên trở thành prefbiến và chúng tôi in trên mục thứ hai (từ [1]) kết thúc bằng dấu cách. Tiếp theo, chúng tôi xây dựng bộ "từ" mới thông qua .join()chức năng hiểu danh sách và chức năng trên một danh sách tạm thời của pref, mỗi từ và chuỗi "1". Bước cuối cùng là in chúng ra


4

Với awk:

awk '{printf("%s ", $2); for(i=3; i<NF; i++) printf("%s:%s:1 ", $1, $i);\
          printf("%s:%s:1\n", $1, $NF)}' file.txt

Đó là tất cả về định dạng các trường được phân tách bằng không gian theo định dạng mong muốn:

  • printf("%s ", $2) in trường thứ hai với dấu cách

  • for(i=3; i<NF; i++) printf("%s:%s:1 ", $1, $i) Lặp lại các trường cuối cùng thứ 3 đến thứ hai và in các trường theo định dạng mong muốn (trường đầu tiên, sau đó là dấu hai chấm, sau đó là trường hiện tại, sau đó là dấu hai chấm, cuối cùng là 1) với khoảng trắng ở cuối

  • printf("%s:%s:1\n", $1, $NF) in trường cuối cùng với dòng mới

Thí dụ:

% cat file.txt
565 0 10 12 23 18 17 25
564 1 7 12 13 16 18 40 29 15

% awk '{printf("%s ", $2); for(i=3; i<NF; i++) printf("%s:%s:1 ", $1, $i); printf("%s:%s:1\n", $1, $NF)}' file.txt
0 565:10:1 565:12:1 565:23:1 565:18:1 565:17:1 565:25:1
1 564:7:1 564:12:1 564:13:1 564:16:1 564:18:1 564:40:1 564:29:1 564:15:1
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.