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

Так, другий до останнього рядка
спудер

Відповіді:


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 -00pe 's/,(?!.*,)//s' file

Пояснення:

  • , Відповідає кома.
  • (?!.*,)Негативний lookahead стверджує, що після цієї збіжної коми не буде кома. Так воно відповідало б останній кома.
  • sІ найбільше імпортує це sмодифікатор DOTALL, який також робить крапку, щоб відповідати навіть символам нового рядка.

2
Ви також можете зробити: perl -0777 -pi -e 's/(.*),(.*?)/\1\2/s'. Це працює, тому що перший .*є жадібним, а другий - нічим.
Олег Васкевич

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

Це повинно видалити лише останнє виникнення ,у будь-якому вхідному файлі - і воно все одно буде друкувати ті, у яких а ,не відбувається. В основному, він буферизує послідовності рядків, які не містять коми.

Коли він стикається з комою, він заміняє буфер поточного рядка буфером утримування і таким чином одночасно друкує всі рядки, що виникли з моменту останньої коми, і звільняє її буфер утримування.

Я щойно переглядав мій файл історії і виявив це:

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'

evalПідзаголовок функції Ed повторно повторюється через всі її аргументи. Під час проходу над ними він ітерує лічильник відповідним чином залежно від контексту кожного перемикача і пропускає через це багато аргументів для наступної ітерації. З цього моменту він робить одну з кількох речей за аргументом:

  • Для кожного варіанта параметр парсер додає $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 - ditto, але для подвійних лапок та глобально.

Отже, щоб продемонструвати, як можна використовувати 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

може бути ефективнішим.


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*]\)тобто пробілами або новою лінією, за якими]

-1

Спробуйте з sedкомандою нижче .

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

1
Це видалить трелі коми з кожного рядка, це не потрібно ОП.
Архемар

@Archemar Ні, він буде видалений лише на останньому рядку, але це не працюватиме для даних ОП, які не є в останньому рядку
αғsnιη
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.