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 2014年

Answers:


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

使用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]
]

使用 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,2014年

是的,第二个版本在Mac上运行
14

4

只需尝试以下Perl一线命令即可。

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

说明:

  • , 匹配逗号。
  • (?!.*,)否定的前瞻断言,在匹配的逗号后将没有逗号。因此它将与最后一个逗号匹配。
  • s最重要的是sDOTALL修饰符,它使点也可以匹配换行符。

2
您也可以这样做:perl -0777 -pi -e 's/(.*),(.*?)/\1\2/s'。之所以有效,.*是因为第一个不是贪婪的,而第二个则不是。
Oleg Vaskevich 2015年

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子外壳遍历其所有参数一次。当遍历它们时,它会根据每个开关的上下文适当地迭代一个计数器,并跳过那么多参数以进行下一次迭代。从那时起,它会根据每个参数执行以下操作之一:

  • 对于每个选项,选项解析器都会添加$a到中$o$a根据$i每个已处理的arg 的值递增arg计数来分配。$a被分配以下两个值之一:
    • a=$((i+=1)) -如果短选项未附加其参数或长选项,则分配该选项。
    • a=$i#-?-如果选项是短选项且确实附加了其arg,则分配该选项。
    • a=\${$a}${1:+$d\${$(($1))\}}-不管初始分配如何,$a的值始终用大括号括起来,并且-在-s某些情况下-有时会$i再增加一个,并附加定界字段。

结果是eval永远不会传递包含任何未知数的字符串。每个命令行参数都由其数字参数编号引用-甚至是从第一个参数的第一个字符中提取的定界符,也是您唯一应使用未转义的字符的分隔符。基本上,该函数是一个宏生成器-它从不以任何特殊方式解释参数的值,因为在解析脚本时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可以很容易地将正则表达式应用于文件中最后一个匹配项之后的数据。我上面运行的命令的结果是:

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选项并将其设为必需,切换到re和repl的多个参数,-s并实现了正确的定界符处理。我认为这是防弹的。我成功地使用了空格和单引号作为分隔符,
mikeserv

2

如果逗号可能不在倒数第二行

使用awktac

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

awk命令是第一次看到模式时执行替换的简单命令。  tac反转文件中各行的顺序,因此该awk命令最终删除了最后一个逗号。

有人告诉我

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

可能会更有效率。


2

如果可以使用tac

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

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

我最好的方法是删除最后一行,并删除逗号后,再次添加] char


1

请尝试以下方法vi

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

说明:

  • $-1 选择倒数第二行

  • s 更换

  • \(,\)\(\_s*]\)查找逗号后跟]并用空格或换行符分隔
  • \2\(\_s*]\)空格或换行符代替,然后用]

-1

请尝试以下sed命令。

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

1
这将从行中删除尾随逗号,这不是OP想要的。
Archemar

@Archemar没有,它会删除只在最后一行,但对于OP的数据将不会工作,这是不是在最后一行
αғsнιη
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.