Jak „upuścić” / usunąć znaki przed ciągiem?

13

Mam ciąg, którym chciałbym manipulować. Ciąg jest w H08W2345678jaki sposób mógłbym nim manipulować, aby wynik był po prostu W2345678?

Podobnie, jeśli chcę usunąć ostatnie 4 znaki, H08W2345678aby uzyskać, H08W234jak to zrobić?

3kstc
źródło
1
Istnieje wiele sposobów manipulowania ciągami. Czy istnieje konkretny powód korzystania sed?
don_crissti,
@don_crissti Bez powodu, oprócz braku doświadczenia. Wszelkie alternatywy są mile widziane ...
3kstc
@don_crissti, historia: z przefiltrowanego pliku CSV biorę jeden z parametrów z wiersza, który jest H08W2345678i muszę go zmanipulować. W2345678Ta wartość z innymi danymi zostanie umieszczona w wysłanej wiadomości e-mail. Ten e-mail zostanie wysłany z cronem.
3kstc,
@don_crissti awking. Tworzę tablicę, a następnie modyfikuję każdy element w tablicy (wszystko inaczej - tj. Zmieniam znacznik czasu Epoki w sekundach na datę itp.)
3kstc
2
Możesz robić takie rzeczy za pomocą awk:printf %s\\n "XX,H08W2345678,YY" | awk -F, '{print substr($2, 4); print substr($2, 1, length($2)-4)}'
don_crissti

Odpowiedzi:

19

Wystarczy użyć bash (lub ksh93skąd pochodzi ta składnia lub zsh):

string="H08W2345678"

echo "${string:3}"
W2345678

echo "${string:0:-4}"
H08W234

Zobacz wiki Wooledge, aby uzyskać więcej informacji na temat manipulacji ciągami .

jasonwryan
źródło
Wymaga to wersji bash 4.2 lub nowszej. Zobacz tę starą kopię Podręcznika użytkownika Bash, Rozdział 3.5.3, „Rozszerzanie parametrów powłoki” lub odpowiedź piskląt tutaj, aby zobaczyć stare ograniczenie („ długość musi być obliczona na liczbę większą lub równą zero.”); … (Ciąg dalszy)
Scott
(Ciąg dalszy)… zobacz Zmiany Bash (na Wiki Bash Hackers) (przewiń w dół do dolnej części sekcji) lub wiadomości Bash w organizacji Technology Infrastructure Services na Uniwersytecie Case Western Reserve (wyszukaj „dodane do bash-4.2” a następnie przewiń w dół do „q.”), aby zobaczyć wersję. …………  "${string:0:${#string}-4}" Działa w wersji bash 4.1, o ile długość $stringwynosi co najmniej 4.
Scott
PS Będzie to również dławić się na takich ciągach znaków abc-e, w których po upuszczeniu pierwszych trzech znaków pozostaniesz -e(ponieważ echo -enie robi tego, co chcesz).
Scott
8
$ echo "H08W2345678" | sed 's/^.\{3\}//'
W2345678

sed 's/^.\{3\}//'znajdzie pierwsze trzy znaki ^.\{3\}i zastąpi je spacją. Tutaj ^.dopasuje dowolny znak na początku łańcucha ( ^wskazuje początek łańcucha) i \{3\}dopasuje poprzedni wzór dokładnie 3 razy. Dopasuje więc ^.\{3\}pierwsze trzy znaki.

$ echo "H08W2345678" | sed 's/.\{4\}$//'
H08W234

Podobnie, sed 's/.\{4\}$//'zastąpi ostatnie cztery znaki spacją ( $wskazuje koniec ciągu).

heemayl
źródło
1
Czy możesz wyjaśnić, 's/^.\{3\}//'a 's/.\{4\}$//'ponieważ wciąż się uczę, bardzo dziękuję
3kstc
@ 3kstc: Sprawdź zmiany
heemayl,
1
Za kilka znaków, użyję ...zamiast .\{3\}od (dla mnie) jest to łatwiejsze do odczytania: sed -e 's/^...//' -e 's/....$//' albo w jednym wyrażeniu z naprzemiennie: sed -r 's/^...|....$//g'. Gdyby usunąć więcej niż kilka znaków, użyłbym tego /.\{17}\/wyrażenia zamiast /.............../.
Johnny
Będzie to źle się zachowywać, jeśli ciąg będzie -elub -n. Oczywiście, znaczenie „drop ostatnie 4 znaki” jest niezdefiniowana dla ciąg znaków krótszym niż 4, ale jeśli ktoś chce przystosować to do spadku pierwszy lub ostatni jeden znak, to może wysadzić.
Scott
2

Jeśli masz plik, w którym każda linia zawiera jedenastoznakowy (lub dowolny inny) ciąg, który chcesz pociąć, sedjest to narzędzie do użycia. Jest w porządku do manipulowania pojedynczym łańcuchem, ale to przesada. W przypadku pojedynczego ciągu odpowiedź Jasona jest prawdopodobnie najlepsza, jeśli masz dostęp do wersji bash 4.2 lub nowszej. Jednak wydaje się , że składnie i są unikalne dla bash (cóż, bash, ksh93, mksh i zsh) - nie widzę ich w Podstawowych specyfikacjach Open Group dla języka poleceń powłoki . Jeśli utkniesz z powłoką zgodną z POSIX, która nie obsługuje rozszerzania podciągów (ekstrakcja), możesz użyć${parameter:offset}${parameter:offset:length}

$ printf "%s\n" "${string#???}"
W2345678

$ printf "%s\n" "${string%????}"
H08W234

używanie printfzamiast echodo ochrony przed ciągami, takimi jak abc-e, gdy upuszczając pierwsze trzy znaki, pozostajesz -e (i echo -enie robi tego, co chcesz).

A jeśli w ogóle nie używasz powłoki z rodziny Bourne (lub używasz starożytnego systemu sprzed POSIX), powinny one nadal działać:

$ expr " $string" : ' ...\(.*\)'
W2345678

$ expr " $string" : ' \(.*\)....'
H08W234

Dodatkowa przestrzeń jest wiodącym w celu uniknięcia problemów z wartościami $string , które są rzeczywiste exproperatorzy (np +,  /,  indexlub match) lub opcji (np  --, --helplub  --version).

Scott
źródło
@ Stéphane Chazelas: (1) Dzięki za przypomnienie mi pułapki, którą znałem około 40 lat temu i jakoś udało mi się zapomnieć. (2) Zawsze rozwiązałem ten problem X; np expr "X$string" : 'X...\(.*\)'. IMO, to łatwiejsze do odczytania i zrozumienia. Czy jest z tym jakiś problem lub powód, aby preferować przestrzeń? (3) Dzisiaj dowiedziałem się, że expr + "$string" : '...\(.*\)'teraz działa. Nie pamiętam tego sprzed 40 lat; czy jest wystarczająco szeroko stosowany, aby go bezpiecznie polecić? (4) Brakowało Ci notki na temat odpowiedzi Jasonwryana i drobiazgowej odpowiedzi Hemayla.
Scott
AFAIK, czyli expr +tylko GNU (nie działa na Solarisie ani ABSICS FreeBSD). Używam spacji zamiast x, ponieważ jest mniej prawdopodobne, że niektóre exprimplementacje będą miały operatory zaczynające się od spacji niż z, xa także dlatego, że jest mniej prawdopodobne, że będą elementy zestawiające, które zaczynają się od spacji niż z x. Ale potem zdaję sobie sprawę, że prawdopodobnie nie jest to dobry wybór do expr " $a" "<" " $b"porównywania ciągów, ponieważ niektóre implementacje kończą porównanie numeryczne, gdy $a/ $bwyglądają jak liczby. Może expr "@@$a"...lub expr "x $a"może być bezpieczniej.
Stéphane Chazelas,
0

Z:

string="H08W2345678"

Dopasowywanie 3 lub 4 znaków wydaje się proste (w przypadku większości powłok):

$ printf '%s\t%s\n' "${string#???}" "${string%????}"
W2345678      H08W234

W przypadku starszych powłok (takich jak powłoka Bourne'a) użyj:

$ string=H08W2345678

$ expr " ${string}" : " ...\(.*\)"
W2345678

$ expr " ${string}" : " \(.*\)...." '
H08W234

Jeśli jest potrzebna liczbowa liczba znaków, użyj:

$ expr " ${string}" : " .\{3\}\(.*\)"
W2345678

$ expr " ${string}" : " \(.*\).\{4\}" '
H08W234

Oczywiście, te wyrażenia regularne działają również z sed, awk i bash 3.0+:

$ echo "$string" | sed 's/^.\{3\}//'
W2345678

$ echo "$string" | sed 's/.\{4\}$//'
H08W234

$ echo "$string" | awk '{sub(/^.{3}/,"")}1'
W2345678

$ echo "$string" | awk '{sub(/.{4}$/,"")}1'
H08W234

$ r='^.{3}(.*)$'; [[ $a =~ $r ]] && echo "${BASH_REMATCH[1]}"
W2345678

$ r='^(.*).{4}$'; [[ $a =~ $r ]] && echo "${BASH_REMATCH[1]}"
H08W234
Izaak
źródło
-1

Jak „upuścić” / usunąć znaki przed ciągiem?

Mam ciąg, którym chciałbym manipulować. Ciąg jest H08W2345678, w jaki sposób mógłbym nim manipulować, aby wynik był tylko W2345678?

echo "H08W2345678" | cut -c 4-
aexl
źródło
To odpowiada tylko na połowę pytania.
Kusalananda
Uważam, że twoje zdanie jest niesprawiedliwe. Ta połowa odpowiada na pytanie, które miałem, kiedy poszukałem w Google POSIX usunąć pierwsze znaki i ta strona pojawiła się w wynikach wyszukiwania. Co więcej, ten tytuł strony obejmuje dokładnie tę samą połowę pytania. Wróciłem i pomogłem, gdy znalazłem rozwiązanie, które mi się podobało - myślę, że ta praca cutjest znacznie bardziej elegancka niż cokolwiek innego na tej stronie.
aexl