sed - loại bỏ sự xuất hiện cuối cùng của chuỗi (dấu phẩy) trong tệp?


15

Tôi có một tệp csv rất lớn. Làm thế nào bạn sẽ loại bỏ cuối cùng ,với sed (hoặc tương tự)?

...
[11911,0,"BUILDER","2014-10-15","BUILDER",0,0],
[11912,0,"BUILDER","2014-10-15","BUILDER",0,0],
[11913,0,"BUILDER","2014-10-15","BUILDER",0,0],
]

Sản phẩm chất lượng

...
[11911,0,"BUILDER","2014-10-15","BUILDER",0,0],
[11912,0,"BUILDER","2014-10-15","BUILDER",0,0],
[11913,0,"BUILDER","2014-10-15","BUILDER",0,0]
]

Lệnh sed sau đây sẽ xóa lần xuất hiện cuối cùng trên mỗi dòng, nhưng tôi muốn mỗi tệp.

sed -e 's/,$//' foo.csv

Cũng không làm việc này

sed '$s/,//' foo.csv

Dấu phẩy luôn nằm trên dòng thứ hai đến cuối cùng phải không?
John1024

Vâng, dòng thứ hai đến dòng cuối cùng
spuder 15/10/14

Câu trả lời:


12

Sử dụng awk

Nếu dấu phẩy luôn ở cuối dòng thứ hai đến dòng cuối cùng:

$ awk 'NR>2{print a;} {a=b; b=$0} END{sub(/,$/, "", a); print a;print b;}'  input
[11911,0,"BUILDER","2014-10-15","BUILDER",0,0],
[11912,0,"BUILDER","2014-10-15","BUILDER",0,0],
[11913,0,"BUILDER","2014-10-15","BUILDER",0,0]
]

Sử dụng awkbash

$ awk -v "line=$(($(wc -l <input)-1))" 'NR==line{sub(/,$/, "")} 1'  input
[11911,0,"BUILDER","2014-10-15","BUILDER",0,0],
[11912,0,"BUILDER","2014-10-15","BUILDER",0,0],
[11913,0,"BUILDER","2014-10-15","BUILDER",0,0]
]

Sử dụng sed

$ sed 'x;${s/,$//;p;x;};1d'  input
[11911,0,"BUILDER","2014-10-15","BUILDER",0,0],
[11912,0,"BUILDER","2014-10-15","BUILDER",0,0],
[11913,0,"BUILDER","2014-10-15","BUILDER",0,0]
]

Đối với OSX và các nền tảng BSD khác, hãy thử:

sed -e x -e '$ {s/,$//;p;x;}' -e 1d  input

Sử dụng bash

while IFS=  read -r line
do
    [ "$a" ] && printf "%s\n" "$a"
    a=$b
    b=$line
done <input
printf "%s\n" "${a%,}"
printf "%s\n" "$b"

Có lẽ nó bởi vì tôi đang trên một mac, nhưng lệnh sed cho lỗised: 1: "x;${s/,$//;p;x}; 2,$ p": extra characters at the end of x command
spuder

@spuder Có, OSX có BSD sedvà nó thường khác nhau theo những cách tinh tế. Tôi không có quyền truy cập vào OSX để kiểm tra điều này, nhưng vui lòng thửsed -n -e x -e '${s/,$//;p;x;}' -e '2,$ p' input
John1024

Vâng, cái thứ hai đó hoạt động trên Mac
spuder

4

Đơn giản là bạn có thể thử lệnh Perl one-liner bên dưới.

perl -00pe 's/,(?!.*,)//s' file

Giải trình:

  • , Ghép dấu phẩy.
  • (?!.*,)Cái nhìn tiêu cực khẳng định rằng sẽ không có dấu phẩy sau dấu phẩy trùng khớp đó. Vì vậy, nó sẽ phù hợp với dấu phẩy cuối cùng.
  • sVà thứ được nhập nhiều nhất là công scụ sửa đổi DOTALL tạo ra dấu chấm để khớp với cả các ký tự dòng mới.

2
Bạn cũng có thể làm : perl -0777 -pi -e 's/(.*),(.*?)/\1\2/s'. Điều này hoạt động bởi vì cái đầu tiên .*là tham lam, trong khi cái thứ hai thì không.
Oleg Vaskevich

4
lcomma() { sed '
    $x;$G;/\(.*\),/!H;//!{$!d
};  $!x;$s//\1/;s/^\n//'
}

Điều đó sẽ chỉ loại bỏ sự xuất hiện cuối cùng của một ,trong bất kỳ tệp đầu vào nào - và nó vẫn sẽ in những lần ,không xảy ra. Về cơ bản, nó đệm các chuỗi các dòng không chứa dấu phẩy.

Khi gặp dấu phẩy, nó hoán đổi bộ đệm dòng hiện tại bằng bộ đệm giữ và theo cách đó đồng thời in ra tất cả các dòng xảy ra kể từ dấu phẩy cuối cùng giải phóng bộ đệm giữ của nó.

Tôi chỉ đào qua tập tin lịch sử của mình và tìm thấy điều này:

lmatch(){ set "USAGE:\
        lmatch /BRE [-(((s|-sub) BRE)|(r|-ref)) REPL [-(f|-flag) FLAG]*]*
"       "${1%"${1#?}"}" "$@"
        eval "${ZSH_VERSION:+emulate sh}"; eval '
        sed "   1x;     \\$3$2!{1!H;\$!d
                };      \\$3$2{x;1!p;\$!d;x
                };      \\$3$2!x;\\$3$2!b'"
        $(      unset h;i=3 p=:-:shfr e='\033[' m=$(($#+1)) f=OPTERR
                [ -t 2 ] && f=$e\2K$e'1;41;17m}\r${h-'$f$e\0m
                f='\${$m?"\"${h-'$f':\t\${$i$e\n}\$1\""}\\c' e=} _o=
                o(){    IFS=\ ;getopts  $p a "$1"       &&
                        [ -n "${a#[?:]}" ]              &&
                        o=${a#-}${OPTARG-${1#-?}}       ||
                        ! eval "o=$f;o=\${o%%*\{$m\}*}"
        };      a(){    case ${a#[!-]}$o in (?|-*) a=;;esac; o=
                        set $* "${3-$2$}{$((i+=!${#a}))${a:+#-?}}"\
                                ${3+$2 "{$((i+=1))$e"} $2
                        IFS=$;  _o=${_o%"${3+$_o} "*}$*\
        };      while   eval "o \"\${$((i+=(OPTIND=1)))}\""
                do      case            ${o#[!$a]}      in
                        (s*|ub)         a s 2 ''        ;;
                        (r*|ef)         a s 2           ;;
                        (f*|lag)        a               ;;
                        (h*|elp)        h= o; break     ;;
                esac;   done;   set -f; printf  "\t%b\n\t" $o $_o
)\"";}

Nó thực sự khá tốt. Vâng, nó sử dụng eval, nhưng nó không bao giờ chuyển bất cứ thứ gì cho nó ngoài một tham chiếu số cho các đối số của nó. Nó xây dựng các sedkịch bản tùy ý để xử lý một trận đấu cuối cùng. Tôi sẽ cho bạn thấy:

printf "%d\" %d' %d\" %d'\n" $(seq 5 5 200) |                               
    tee /dev/fd/2 |                                                         
    lmatch  d^.0     \  #all re's delimit w/ d now                           
        -r '&&&&'    \  #-r or --ref like: '...s//$ref/...'      
        --sub \' sq  \  #-s or --sub like: '...s/$arg1/$arg2/...'
        --flag 4     \  #-f or --flag appended to last -r or -s
        -s\" \\dq    \  #short opts can be '-s $arg1 $arg2' or '-r$arg1'
        -fg             #tacked on so: '...s/"/dq/g...'                     

Mà in sau đây để stderr. Đây là bản sao lmatchđầu vào của:

5" 10' 15" 20'
25" 30' 35" 40'
45" 50' 55" 60'
65" 70' 75" 80'
85" 90' 95" 100'
105" 110' 115" 120'
125" 130' 135" 140'
145" 150' 155" 160'
165" 170' 175" 180'
185" 190' 195" 200'

Hàm con evaled của hàm lặp lại thông qua tất cả các đối số của nó một lần. Khi nó đi qua chúng, nó lặp lại một bộ đếm một cách thích hợp tùy thuộc vào ngữ cảnh cho mỗi công tắc và bỏ qua nhiều đối số cho lần lặp tiếp theo. Từ đó trở đi, một trong một vài điều cho mỗi đối số:

  • Đối với mỗi tùy chọn, trình phân tích cú pháp tùy chọn thêm $avào $o. $ađược chỉ định dựa trên giá trị $iđược tăng theo số lượng arg cho mỗi arg được xử lý. $ađược gán một trong hai giá trị sau:
    • a=$((i+=1)) - điều này được chỉ định nếu một tùy chọn ngắn không có đối số của nó được nối với nó hoặc nếu tùy chọn đó là một đối số dài.
    • a=$i#-?- điều này được gán nếu tùy chọn là một trong ngắn hạn và không có arg của nó nối vào nó.
    • a=\${$a}${1:+$d\${$(($1))\}}- Bất kể chỉ định ban đầu, $agiá trị của luôn được gói trong dấu ngoặc nhọn và - trong -strường hợp - đôi khi $iđược tăng thêm một trường nữa và trường được phân cách bổ sung được thêm vào.

Kết quả là evalkhông bao giờ được thông qua một chuỗi có chứa bất kỳ ẩn số nào. Mỗi đối số dòng lệnh được gọi bằng số đối số số của chúng - ngay cả dấu phân cách được trích xuất từ ​​ký tự đầu tiên của đối số đầu tiên và là lần duy nhất bạn nên sử dụng bất kỳ ký tự nào không được bỏ qua. Về cơ bản, hàm là một trình tạo macro - nó không bao giờ diễn giải các giá trị của các đối số theo bất kỳ cách đặc biệt nào vì dĩ nhiên,sed có thể (và sẽ) dễ dàng xử lý điều đó khi phân tích cú pháp kịch bản. Thay vào đó, nó chỉ hợp lý sắp xếp các đối số của nó thành một kịch bản khả thi.

Đây là một số đầu ra gỡ lỗi của chức năng tại nơi làm việc:

... sed "   1x;\\$2$1!{1!H;\$!d
        };      \\$2$1{x;1!p;\$!d;x
        };      \\$2$1!x;\\$2$1!b
        s$1$1${4}$1
        s$1${6}$1${7}$1${9}
        s$1${10#-?}$1${11}$1${12#-?}
        "
++ sed '        1x;\d^.0d!{1!H;$!d
        };      \d^.0d{x;1!p;$!d;x
        };      \d^.0d!x;\d^.0d!b
        sdd&&&&d
        sd'\''dsqd4
        sd"d\dqdg
        '

Và do đó, lmatchcó thể được sử dụng để dễ dàng áp dụng regexes cho dữ liệu sau lần khớp cuối cùng trong một tệp. Kết quả của lệnh tôi chạy ở trên là:

5" 10' 15" 20'
25" 30' 35" 40'
45" 50' 55" 60'
65" 70' 75" 80'
85" 90' 95" 100'
101010105dq 110' 115dq 120'
125dq 130' 135dq 140sq
145dq 150' 155dq 160'
165dq 170' 175dq 180'
185dq 190' 195dq 200'

... trong đó, với tập hợp con của đầu vào tệp theo sau lần cuối /^.0/được khớp, áp dụng các thay thế sau:

  • sdd&&&&d- thay thế $matchbằng chính nó 4 lần.
  • sd'dsqd4 - trích dẫn đơn thứ tư sau đầu dòng kể từ trận đấu cuối cùng.
  • sd"d\dqd2 - ditto, nhưng cho dấu ngoặc kép và toàn cầu.

Và vì vậy, để chứng minh cách người ta có thể sử dụng lmatchđể xóa dấu phẩy cuối cùng trong tệp:

printf "%d, %d %d, %d\n" $(seq 5 5 100) |
lmatch '/\(.*\),' -r\\1

ĐẦU RA:

5, 10 15, 20
25, 30 35, 40
45, 50 55, 60
65, 70 75, 80
85, 90 95 100

1
@don_crissti - bây giờ thì tốt hơn - Tôi đã bỏ -mtùy chọn và bắt buộc, chuyển sang nhiều đối số để re và thay thế -svà cũng thực hiện xử lý dấu phân cách thích hợp. Tôi nghĩ đó là chống đạn. Tôi đã sử dụng thành công cả một khoảng
trắng

2

Nếu dấu phẩy có thể không nằm trên dòng thứ hai đến cuối cùng

Sử dụng awktac:

tac foo.csv | awk '/,$/ && !handled { sub(/,$/, ""); handled++ } {print}' | tac

Các awklệnh là một trong những đơn giản để làm sự thay thế lần đầu tiên mô hình được xem.  tacđảo ngược thứ tự của các dòng trong tệp, vì vậy awklệnh kết thúc xóa dấu phẩy cuối cùng .

Tôi đã được nói rằng

tac foo.csv | awk '/,$/ && !handled { sub(/,$/, ""); handled++ } {print}' > tmp && tac tmp

có thể hiệu quả hơn


2

Nếu bạn có thể sử dụng tac:

tac file | perl -pe '$_=reverse;!$done && s/,// && $done++;$_=reverse'|tac

1

xem /programming/12390134/remove-comma-from-last-line

Điều này làm việc cho tôi:

$cat input.txt
{"name": "secondary_ua","type":"STRING"},
{"name": "request_ip","type":"STRING"},
{"name": "cb","type":"STRING"},
$ sed '$s/,$//' < input.txt >output.txt
$cat output.txt
{"name": "secondary_ua","type":"STRING"},
{"name": "request_ip","type":"STRING"},
{"name": "cb","type":"STRING"}

Cách tốt nhất của tôi là xóa dòng cuối cùng và sau khi xóa dấu phẩy, hãy thêm lại] char


1

Hãy thử với bên dưới vi:

  vi "+:$-1s/\(,\)\(\_s*]\)/\2/e" "+:x" file

Giải trình:

  • $-1 chọn thứ hai đến dòng cuối cùng

  • s thay thế

  • \(,\)\(\_s*]\)tìm một dấu phẩy theo sau ]và cách nhau bởi dấu cách hoặc dòng mới
  • \2thay thế bằng \(\_s*]\)khoảng trắng hoặc dòng mới theo sau]

-1

Hãy thử với sedlệnh dưới đây .

sed -i '$s/,$//' foo.csv

1
Điều này sẽ loại bỏ dấu phẩy từ mỗi dòng, đây không phải là OP muốn.
Archemar

@Archemar Không, nó sẽ chỉ xóa trên dòng cuối cùng nhưng sẽ không hoạt động đối với dữ liệu của OP không nằm ở dòng cuối cùng
ngày
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.