sed - ลบการเกิดขึ้นครั้งสุดท้ายของสตริง (เครื่องหมายจุลภาค) ในไฟล์หรือไม่


15

ฉันมีไฟล์ csv ที่มีขนาดใหญ่มาก คุณจะลบส่วนสุดท้าย,ด้วย sed (หรือคล้ายกัน) ได้อย่างไร

...
[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],
]

ผลลัพธ์ที่ต้องการ

...
[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]
]

คำสั่ง sed ต่อไปนี้จะลบการเกิดขึ้นครั้งสุดท้ายต่อบรรทัด แต่ฉันต้องการต่อไฟล์

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

ไม่ทำงานนี้

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

เครื่องหมายจุลภาคอยู่บนบรรทัดที่สองถึงครั้งสุดท้ายเสมอหรือไม่
John1024

ใช่บรรทัดที่สองถึงครั้งสุดท้าย
spuder

คำตอบ:


12

การใช้ awk

หากเครื่องหมายจุลภาคอยู่ท้ายบรรทัดที่สองถึงบรรทัดสุดท้ายเสมอ:

$ 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]
]

การใช้awkและbash

$ 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]
]

การใช้ 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]
]

สำหรับ OSX และแพลตฟอร์ม BSD อื่น ๆ ลอง:

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

การใช้ 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"

อาจเป็นเพราะฉันใช้ mac แต่คำสั่ง sed ให้ข้อผิดพลาดsed: 1: "x;${s/,$//;p;x}; 2,$ p": extra characters at the end of x command
spuder

@spuder ใช่ OSX มี BSD sedและมักจะแตกต่างกันในรูปแบบที่ลึกซึ้ง ฉันไม่สามารถเข้าถึง OSX เพื่อทดสอบสิ่งนี้ แต่โปรดลองsed -n -e x -e '${s/,$//;p;x;}' -e '2,$ p' input
John1024

ใช่คนที่สองทำงานบน Mac
spuder

4

เพียงคุณสามารถลองใช้คำสั่ง Perl ด้านล่างของ Perl

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

คำอธิบาย:

  • , ตรงกับเครื่องหมายจุลภาค
  • (?!.*,)Lookahead เชิงลบยืนยันว่าจะไม่มีเครื่องหมายจุลภาคหลังจากเครื่องหมายจุลภาคที่ตรงกัน ดังนั้นมันจะตรงกับเครื่องหมายจุลภาคสุดท้าย
  • sและสิ่งที่นำเข้ามากที่สุดคือsDOTALL โมดิฟายเออร์ซึ่งทำให้ dot ตรงกับแม้แต่อักขระบรรทัดใหม่

2
perl -0777 -pi -e 's/(.*),(.*?)/\1\2/s'นอกจากนี้คุณยังสามารถทำ: สิ่งนี้ได้ผลเพราะอันแรก.*โลภในขณะที่อันที่สองไม่ใช่
Oleg Vaskevich

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

ซึ่งควรลบเฉพาะการเกิดขึ้นครั้งสุดท้ายของ a ,ในไฟล์อินพุตใด ๆ - และมันจะยังคงพิมพ์สิ่งที่ a,ไม่เกิดขึ้น โดยพื้นฐานแล้วบัฟเฟอร์จะเรียงลำดับของบรรทัดที่ไม่มีเครื่องหมายจุลภาค

เมื่อพบเครื่องหมายจุลภาคจะทำการสลับบัฟเฟอร์บรรทัดปัจจุบันกับบัฟเฟอร์พักไว้และด้วยวิธีดังกล่าวจะพิมพ์บรรทัดทั้งหมดที่เกิดขึ้นตั้งแต่เครื่องหมายจุลภาคสุดท้ายและปล่อยบัฟเฟอร์พักไว้พร้อมกัน

ฉันเพิ่งขุดไฟล์ประวัติของฉันและพบสิ่งนี้:

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
)\"";}

มันค่อนข้างดีจริงๆ ใช่มันใช้evalแต่ไม่เคยส่งผ่านอะไรเลยนอกเหนือจากการอ้างอิงตัวเลขไปยังอาร์กิวเมนต์ มันสร้างsedสคริปต์โดยพลการสำหรับการจัดการนัดสุดท้าย ฉันจะแสดงให้คุณ:

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...'                     

ที่พิมพ์ข้อมูลต่อไปนี้ไปยัง stderr นี่คือสำเนาของlmatchอินพุต:

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'

ฟังก์ชั่นย่อยของevaled จะวนซ้ำผ่านการขัดแย้งทั้งหมดในครั้งเดียว ขณะที่เดินข้ามมันจะนับตัวนับตามความเหมาะสมโดยขึ้นอยู่กับบริบทของแต่ละสวิตช์และข้ามข้อโต้แย้งจำนวนมากสำหรับการทำซ้ำครั้งถัดไป จากนั้นจะทำหนึ่งในสองสามสิ่งต่ออาร์กิวเมนต์:

  • สำหรับแต่ละตัวเลือก parser ตัวเลือกในการเพิ่มการ$a ถูกกำหนดโดยยึดตามค่าที่เพิ่มขึ้นโดยหาเรื่องจำนวนสำหรับแต่ละ ARG ประมวลผล ถูกกำหนดหนึ่งในสองค่าต่อไปนี้: $o$a$i$a
    • a=$((i+=1)) - สิ่งนี้จะถูกกำหนดถ้าตัวเลือกแบบสั้นไม่มีอาร์กิวเมนต์ต่อท้ายหรือถ้าตัวเลือกนั้นยาว
    • a=$i#-?- สิ่งนี้จะถูกกำหนดถ้าตัวเลือกนั้นสั้นและไม่มี ARGG ผนวกอยู่กับมัน
    • a=\${$a}${1:+$d\${$(($1))\}}- โดยไม่คำนึงถึงการกำหนดเริ่มต้น$aค่าของจะถูกห่อด้วยเครื่องหมายปีกกาเสมอและ - ใน-sกรณี - บางครั้ง$iจะเพิ่มขึ้นอีกหนึ่งเขตข้อมูลที่มีการคั่นและเพิ่มเติม

ผลที่ได้evalคือไม่เคยผ่านสตริงที่มีไม่ทราบใด ๆ อาร์กิวเมนต์บรรทัดรับคำสั่งแต่ละรายการอ้างอิงโดยหมายเลขอาร์กิวเมนต์ตัวเลข - แม้แต่ตัวคั่นที่แยกออกมาจากอักขระตัวแรกของอาร์กิวเมนต์แรกและเป็นครั้งเดียวที่คุณควรใช้อักขระใด ๆ ที่ไม่ใช้ Escape โดยพื้นฐานแล้วฟังก์ชั่นนี้เป็นเครื่องกำเนิดไฟฟ้ามาโคร - มันไม่เคยตีความค่าของอาร์กิวเมนต์ในรูปแบบพิเศษเพราะsedสามารถ(และจะแน่นอน)จัดการได้อย่างง่ายดายเมื่อมันแยกวิเคราะห์สคริปต์ แต่มันเพียงแค่จัดเรียง args ให้เข้ากับสคริปต์ได้

นี่คือข้อบกพร่องบางส่วนของฟังก์ชันที่ทำงาน:

... 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
        '

และlmatchสามารถใช้เพื่อนำ regexes ไปใช้กับข้อมูลได้อย่างง่ายดายหลังจากการแข่งขันครั้งสุดท้ายในไฟล์ ผลลัพธ์ของคำสั่งที่ฉันใช้ด้านบนคือ:

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'

... ซึ่งให้เซตย่อยของอินพุตไฟล์ที่ตามหลังการ/^.0/จับคู่ครั้งล่าสุดใช้การแทนที่ต่อไปนี้:

  • sdd&&&&d- แทนที่$matchด้วยตัวเอง 4 ครั้ง
  • sd'dsqd4 - คำพูดเดี่ยวที่สี่ตามหลังจุดเริ่มต้นของบรรทัดตั้งแต่นัดสุดท้าย
  • sd"d\dqd2 - เหมือนกัน แต่สำหรับเครื่องหมายคำพูดคู่และทั่วโลก

ดังนั้นเพื่อแสดงวิธีที่อาจใช้lmatchในการลบเครื่องหมายจุลภาคสุดท้ายในไฟล์:

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

เอาท์พุท:

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

1
@don_crissti - วิธีนี้ดีกว่าตอนนี้ - ฉัน-mเลือกตัวเลือกนี้และบังคับให้เปลี่ยนเป็นอาร์กิวเมนต์หลายครั้งสำหรับการคัดลอกซ้ำและ-sใช้การจัดการตัวคั่นที่เหมาะสม ฉันคิดว่ามันกันกระสุน ฉันใช้พื้นที่และเครื่องหมายคำพูดเดียวเป็นตัวคั่นสำเร็จ
mikeserv

2

หากเครื่องหมายจุลภาคอาจไม่อยู่ในบรรทัดที่สองถึงครั้งสุดท้าย

การใช้awkและtac:

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

awkคำสั่งเป็นหนึ่งที่ง่ายที่จะทำเปลี่ยนตัวครั้งแรกรูปแบบจะเห็น  tacกลับลำดับของบรรทัดในไฟล์ดังนั้นawkคำสั่งจะสิ้นสุดการลบไฟล์ล่าสุดเครื่องหมายจุลภาค

ฉันได้รับการบอกว่า

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

อาจมีประสิทธิภาพมากขึ้น



1

ดู/programming/12390134/remove-comma-from-last-line

นี่ใช้ได้สำหรับฉัน:

$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"}

วิธีที่ดีที่สุดของฉันคือลบบรรทัดสุดท้ายและหลังจากลบเครื่องหมายจุลภาคให้เพิ่ม] อักขระอีกครั้ง


1

ลองด้วยด้านล่างvi:

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

คำอธิบาย:

  • $-1 เลือกบรรทัดที่สองถึงบรรทัดสุดท้าย

  • s แทนที่

  • \(,\)\(\_s*]\)ค้นหาเครื่องหมายจุลภาคตามด้วย]และคั่นด้วยช่องว่างหรือขึ้นบรรทัดใหม่
  • \2แทนที่ด้วย\(\_s*]\)ช่องว่างหรือขึ้นบรรทัดใหม่แล้วตามด้วย]

-1

ลองด้วยsedคำสั่งด้านล่าง

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

1
นี้จะลบเครื่องหมายจุลภาค trailling จากทุกบรรทัดนี่ไม่ใช่สิ่งที่ OP ต้องการ
Archemar

@Archemar ไม่มันจะลบเฉพาะในบรรทัดสุดท้าย แต่จะไม่ทำงานกับข้อมูลของ OP ที่ไม่ได้อยู่ในบรรทัดสุดท้าย
αғsнιη
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.