sed - 删除文件中最后一次出现的字符串(逗号)?

sed - 删除文件中最后一次出现的字符串(逗号)?

我有一个非常大的 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

答案1

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

答案2

您可以简单地尝试下面的 Perl 一行命令。

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

解释:

  • ,匹配逗号。
  • (?!.*,)负向先行断言在匹配的逗号之后不会有逗号。所以它会匹配最后一个逗号。
  • s最重要的是sDOTALL 修饰符,它使点甚至可以匹配换行符。

答案3

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

  • 对于每个选项,选项解析器都会添加$a$o.$a根据处理的每个 arg 的值$i按 arg count 递增进行分配。$a被分配以下两个值之一:
    • a=$((i+=1))- 如果短选项没有附加其参数或者选项是长选项,则分配此选项。
    • a=$i#-?- 如果选项是短选项并且则分配此选项将其 arg 附加到其上。
    • a=\${$a}${1:+$d\${$(($1))\}}- 无论初始分配如何,$a的值始终用大括号括起来,并且 - 在某种-s情况下 - 有时$i会再增加一个,并附加分隔字段。

结果是eval永远不会传递包含任何未知数的字符串。每个命令行参数都由其数字参数编号引用 - 甚至是从第一个参数的第一个字符中提取的分隔符,并且这是您唯一应该使用未转义的字符的时间。基本上,该函数是一个宏生成器 - 它从不以任何特殊方式解释参数的值,因为sed可以(当然,也会)当它解析脚本时很容易处理这个问题。相反,它只是明智地将其参数排列成一个可行的脚本。

以下是该函数工作时的一些调试输出:

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

答案4

https://stackoverflow.com/questions/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"}

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

相关内容