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
spuder
источник
Всегда ли запятая находится на второй строке?
John1024 15.10.14
Да, от второй до последней строки
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"
John1024
источник
Может быть, потому что я на 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 one-liner.

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

Объяснение:

  • , Соответствует запятой.
  • (?!.*,)Отрицательный взгляд утверждает, что после этой соответствующей запятой не будет запятой. Так что это будет соответствовать последней запятой.
  • sИ самая sважная вещь - это модификатор DOTALL, который делает точку подходящей даже для символов новой строки.
Авинаш Радж
источник
2
Вы также можете сделать: perl -0777 -pi -e 's/(.*),(.*?)/\1\2/s'. Это работает, потому что первый .*жадный, а второй нет.
Олег Васкевич
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 subshell повторяет все свои аргументы один раз. Проходя по ним, он выполняет итерацию счетчика в зависимости от контекста для каждого переключателя и пропускает столько аргументов для следующей итерации. С тех пор он делает одну из нескольких вещей на аргумент:

  • Для каждой опции парсер опций добавляет $aк $o. $aназначается на основе значения, $iкоторое увеличивается на число аргументов для каждого обработанного аргумента.$aприсваивается одно из двух следующих значений:
    • a=$((i+=1)) - это присваивается, если к короткой опции не добавлен аргумент, или если опция была длинной.
    • a=$i#-?- это назначается , если опция является коротким и вовсе есть ее аргумент прилагается к нему.
    • 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
mikeserv
источник
1
@don_crissti - теперь это стало лучше - я убрал -mопцию и сделал ее обязательной, переключился на несколько аргументов для re и repl для, -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

может быть более эффективным.

G-Man говорит: «Восстанови Монику»
источник
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"}

Мой лучший способ - удалить последнюю строку и после удаления запятой добавить символ] снова

Ю Цзяо
источник
1

Попробуйте с ниже vi:

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

Объяснение:

  • $-1 выберите вторую строку до последней

  • s замещать

  • \(,\)\(\_s*]\)найти запятую, за которой ]следует пробел или символ новой строки
  • \2заменить \(\_s*]\)то есть пробелами или символом новой строки с последующим]
knisterstern
источник
-1

Попробуйте с приведенной ниже sedкомандой.

sed -i '$s/,$//' foo.csv
Сэчин
источник
1
Это удалит запятую с каждой строки, это не то, что нужно OP.
Архемар
@Archemar Нет, он будет удален только в последней строке, но это не сработает для данных OP, которых нет в последней строке
αғsнιη