sed - usunąć ostatnie wystąpienie ciągu (przecinek) w pliku?

15

Mam bardzo duży plik csv. Jak ,usunąłbyś ostatni przy pomocy sed (lub podobnego)?

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

Pożądane wyjście

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

Następujące polecenie sed usunie ostatnie wystąpienie w wierszu, ale chcę na plik.

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

To też nie działa

sed '$s/,//' foo.csv
spuder
źródło
Czy przecinek jest zawsze przedostatni?
John1024
Tak, od drugiej do ostatniej linii
spuder

Odpowiedzi:

12

Za pomocą awk

Jeśli przecinek jest zawsze na końcu drugiego do ostatniego wiersza:

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

Korzystanie awkibash

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

Za pomocą 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]
]

W przypadku OSX i innych platform BSD spróbuj:

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

Za pomocą 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
źródło
Może to dlatego, że jestem na komputerze Mac, ale polecenie sed daje błądsed: 1: "x;${s/,$//;p;x}; 2,$ p": extra characters at the end of x command
spuder
@spuder Tak, OSX ma BSD sedi często różni się subtelnie. Nie mam dostępu do OSX, aby to przetestować, ale spróbujsed -n -e x -e '${s/,$//;p;x;}' -e '2,$ p' input
John1024,
Tak, ten drugi działał na Macu
spuder
4

Po prostu możesz wypróbować poniższe polecenie jednowierszowe Perla.

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

Wyjaśnienie:

  • , Dopasowuje przecinek.
  • (?!.*,)Negatywne spojrzenie wstecz zapewnia, że ​​po tym dopasowanym przecinku nie będzie przecinka. Więc pasowałby do ostatniego przecinka.
  • sA najbardziej importującym jest smodyfikator DOTALL, który sprawia, że ​​kropka pasuje również do znaków nowego wiersza.
Avinash Raj
źródło
2
Można również zrobić: perl -0777 -pi -e 's/(.*),(.*?)/\1\2/s'. Działa to, ponieważ pierwszy .*jest chciwy, a drugi nie.
Oleg Waszkiewicz
4
lcomma() { sed '
    $x;$G;/\(.*\),/!H;//!{$!d
};  $!x;$s//\1/;s/^\n//'
}

To powinno usunąć tylko ostatnie wystąpienie ,dowolnego pliku wejściowego - i nadal będzie drukować te, w których ,nie występuje. Zasadniczo buforuje sekwencje linii, które nie zawierają przecinka.

Kiedy napotka przecinek, zamienia bieżący bufor linii z buforem wstrzymania i w ten sposób jednocześnie drukuje wszystkie linie, które wystąpiły od ostatniego przecinka i zwalnia bufor wstrzymania.

Właśnie przeglądałem mój plik historii i znalazłem to:

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

To jest całkiem niezłe. Tak, używa eval, ale nigdy nie przekazuje mu niczego poza odniesieniem numerycznym do swoich argumentów. Buduje dowolne sedskrypty do obsługi ostatniego dopasowania. Pokażę ci:

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

Wypisuje to na stderr. To jest kopia danych lmatchwejściowych:

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'

evalPodskładka ed funkcji iteruje wszystkie argumenty jeden raz. Przechodząc nad nimi, odpowiednio iteruje licznik w zależności od kontekstu dla każdego przełącznika i pomija tak wiele argumentów do następnej iteracji. Odtąd robi jedną z kilku rzeczy na argument:

  • Dla każdej opcji parser opcja dodaje $asię $o. $ajest przypisywany na podstawie wartości, $iktóra jest zwiększana o liczbę arg dla każdego przetworzonego arg. $ama przypisaną jedną z dwóch następujących wartości:
    • a=$((i+=1)) - jest to przypisywane, jeśli do opcji krótkiej nie dołączono argumentu lub jeśli opcja była długa.
    • a=$i#-?- to jest przypisany jeśli opcja jest krótka i nie ma jej arg dołączana do niego.
    • a=\${$a}${1:+$d\${$(($1))\}}- Bez względu na początkowe przypisanie, $awartość jest zawsze zawijana w nawiasy klamrowe i - w takim -sprzypadku - czasami $ijest zwiększana o jeszcze jedno i dołączane jest dodatkowo pole rozdzielane.

Powoduje to, że evalnigdy nie jest przekazywany ciąg zawierający nieznane. Do każdego z argumentów wiersza polecenia odwołuje się ich numeryczny numer argumentu - nawet separator, który jest wyodrębniany z pierwszego znaku pierwszego argumentu i jest jedynym czasem, w którym powinieneś użyć dowolnego znaku, który jest nieskalowany. Zasadniczo funkcja jest generatorem makr - nigdy nie interpretuje wartości argumentów w żaden specjalny sposób, ponieważ sedmoże (i oczywiście) z łatwością poradzi sobie z tym podczas analizy skryptu. Zamiast tego rozsądnie układa argumenty w praktyczny skrypt.

Oto niektóre dane wyjściowe debugowania funkcji w pracy:

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

Dzięki temu lmatchmożna łatwo zastosować wyrażenia regularne do danych po ostatnim dopasowaniu w pliku. Wynik polecenia, które uruchomiłem powyżej, to:

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'

... który, biorąc pod uwagę podzbiór danych wejściowych pliku, który następuje po ostatnim /^.0/dopasowaniu, stosuje następujące podstawienia:

  • sdd&&&&d- zastępuje $matchsię 4 razy.
  • sd'dsqd4 - czwarty pojedynczy cytat następujący po początku wiersza od ostatniego meczu.
  • sd"d\dqd2 - to samo, ale w przypadku podwójnych cytatów i globalnie.

Aby więc pokazać, jak można użyć lmatchostatniego przecinka w pliku:

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

WYNIK:

5, 10 15, 20
25, 30 35, 40
45, 50 55, 60
65, 70 75, 80
85, 90 95 100
mikeserv
źródło
1
@don_crissti - teraz jest o wiele lepiej - porzuciłem -mopcję i uczyniłem ją obowiązkową, przełączyłem się na wiele argumentów dla re i repl dla, -sa także zaimplementowałem poprawną obsługę separatora. Myślę, że jest kuloodporny. Z powodzeniem użyłem zarówno spacji, jak i pojedynczego cudzysłowu jako separatora,
mikeserv
2

Jeśli przecinek może nie znajdować się w wierszu od drugiego do ostatniego

Korzystanie awki tac:

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

awkKomenda jest prosta do zrobienia podstawienie za pierwszym razem wzór jest widoczny.  tacodwraca kolejność wierszy w pliku, więc awkpolecenie kończy usuwanie ostatniego przecinka.

Powiedziano mi to

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

może być bardziej wydajny.

G-Man mówi „Przywróć Monikę”
źródło
2

Jeśli możesz użyć tac:

tac file | perl -pe '$_=reverse;!$done && s/,// && $done++;$_=reverse'|tac
Joseph R.
źródło
1

widzieć /programming/12390134/remove-comma-from-last-line

To działa dla mnie:

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

Moim najlepszym sposobem jest usunięcie ostatniej linii i po usunięciu przecinka dodaj ponownie znak]

Yu Jiaao
źródło
1

Wypróbuj poniżej vi:

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

Wyjaśnienie:

  • $-1 wybierz od drugiej do ostatniej linii

  • s zastąpić

  • \(,\)\(\_s*]\) znajdź przecinek, a następnie ] i oddziel je spacjami lub znakiem nowej linii
  • \2zastąp przez \(\_s*]\)np. spacje lub znak nowej linii, a następnie]
knisterstern
źródło
-1

Spróbuj z poniższym sedpoleceniem.

sed -i '$s/,$//' foo.csv
Sachin
źródło
1
Spowoduje to usunięcie przecinka trailingowego z każdej linii, nie jest to potrzebne OP.
Archemar
@Archemar Nie, usunie tylko w ostatniej linii, ale to nie zadziała dla danych OP, które nie znajdują się w ostatniej linii
αғsнιη