Jak wyodrębnić pierwsze dwa znaki łańcucha w skryptach powłoki?

123

Na przykład, biorąc pod uwagę:

USCAGoleta9311734.5021-120.1287855805

Chcę tylko wyodrębnić:

US
Greg
źródło
6
Dziękuję wszystkim. Skończyło się na użyciu „cut -c1-2”, szczerze mówiąc, nawet nie wiedziałem, że „cut” tam jest. Chciałbym powiedzieć, że mam duże doświadczenie w wierszu poleceń, ale najwyraźniej muszę się wiele nauczyć.
Greg,
1
@Greg, pamiętaj tylko, że cięcie jest uruchamiane jako oddzielny proces - będzie wolniejsze niż rozwiązanie wewnętrznego bash, które zamieściłem obok niego w mojej odpowiedzi. To nie ma znaczenia, chyba że przetwarzasz ogromne zestawy danych, ale musisz o tym pamiętać.
paxdiablo
Edytuj Właściwie myślę, że ta linia kodu zostanie prawdopodobnie wykonana około 50 000 razy na raport. Więc może po prostu skorzystam z wewnętrznej metody Bash - która, jak powiedziałeś, pozwoli zaoszczędzić trochę bardzo potrzebnych zasobów.
Greg,
related: unix.stackexchange.com/questions/3454/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Odpowiedzi:

180

Prawdopodobnie najbardziej wydajną metodą, jeśli używasz bashpowłoki (i wydaje się, że tak jest, na podstawie twoich komentarzy), jest użycie wariantu podłańcucha rozwijania parametrów:

pax> long="USCAGol.blah.blah.blah"
pax> short="${long:0:2}" ; echo "${short}"
US

Będą shortto pierwsze dwa znaki long. Jeśli longjest krótszy niż dwa znaki, shortbędzie identyczny.

Ta metoda w powłoce jest zwykle lepsza, jeśli zamierzasz to robić dużo (np. 50000 razy na raport, jak wspomniałeś), ponieważ nie ma narzutu związanego z tworzeniem procesu. Wszystkie rozwiązania korzystające z programów zewnętrznych będą cierpieć z powodu tego obciążenia.

Jeśli chcesz również zapewnić minimalną długość, możesz wyłożyć ją przed ręką za pomocą czegoś takiego:

pax> long="A"
pax> tmpstr="${long}.."
pax> short="${tmpstr:0:2}" ; echo "${short}"
A.

Zapewniłoby to, że cokolwiek o długości mniejszej niż dwa znaki zostało dopełnione po prawej stronie kropkami (lub czymś innym, po prostu zmieniając znak używany podczas tworzenia tmpstr). Nie jest jasne, czy tego potrzebujesz, ale pomyślałem, że wstawię to dla kompletności.


Powiedziawszy to, istnieje wiele sposobów, aby to zrobić za pomocą programów zewnętrznych (na przykład, jeśli nie masz bashdostępnych), z których niektóre to:

short=$(echo "${long}" | cut -c1-2)
short=$(echo "${long}" | head -c2)
short=$(echo "${long}" | awk '{print substr ($0, 0, 2)}'
short=$(echo "${long}" | sed 's/^\(..\).*/\1/')

Pierwsze dwa ( cuti head) są identyczne dla ciągu jednowierszowego - w zasadzie oba zwracają po prostu pierwsze dwa znaki. Różnią się tym, cutże dadzą ci pierwsze dwa znaki w każdej linii i headdadzą ci pierwsze dwa znaki z całego wejścia

Trzecia używa funkcji awkpodłańcucha do wyodrębnienia pierwszych dwóch znaków, a czwarta używa sedgrup przechwytywania (przy użyciu ()i \1) do przechwycenia pierwszych dwóch znaków i zastąpienia nimi całego wiersza. Oba są podobne cut- dostarczają pierwsze dwa znaki z każdego wiersza na wejściu.

Nic z tego nie ma znaczenia, jeśli masz pewność, że dane wejściowe to jedna linia, wszystkie mają identyczny efekt.

paxdiablo
źródło
Wolałbym użyć printf '%s'zamiast echow przypadku istnieją dziwne znaki w ciągu: stackoverflow.com/a/40423558/895245 Dla POSIX obsesję: head -cnie POSIX, cut -ca awk substrto, sed \1nie jestem pewien.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
@CiroSantilli 新疆 改造 中心 996ICU 六四 事件 używając printf, nie potrzebujesz nawet dodatkowego programu. Zobacz moją odpowiedź .
bschlueter
60

najłatwiej jest

${string:position:length}

Gdzie to wyodrębnia $lengthpodciąg z $stringat $position.

Jest to wbudowana funkcja bash, więc awk lub sed nie są wymagane.

ennuikiller
źródło
To jest krótki, słodki i najłatwiejszy sposób na zdobycie podciągu.
ani627
34

Musisz zdobyć kilka dobrych odpowiedzi i pójdę z Basha wbudowane siebie, ale skoro pytasz o seda awki ( prawie ) nikt inny nie zaproponował rozwiązania oparte na nich, ofiaruję Ci te:

echo "USCAGoleta9311734.5021-120.1287855805" | awk '{print substr($0,0,2)}'

i

echo "USCAGoleta9311734.5021-120.1287855805" | sed 's/\(^..\).*/\1/'

To awkpowinno być dość oczywiste, ale oto wyjaśnienie sedjednego:

  • zastąp „s /”
  • grupa „()” składająca się z dwóch dowolnych znaków „..” zaczynająca się na początku wiersza „^”, po której następuje dowolny znak „”. powtórzone zero lub więcej razy „*” (ukośniki odwrotne są potrzebne do zmiany znaczenia niektórych znaków specjalnych)
  • przez „/” zawartość pierwszej (i jedynej w tym przypadku) grupy (w tym przypadku ukośnik odwrotny jest specjalnym znakiem ucieczki odnoszącym się do pasującego wyrażenia podrzędnego)
  • Gotowe "/"
Wstrzymano do odwołania.
źródło
1
W awk łańcuchy zaczynają się od indeksu 1, więc powinieneś użyć substr($0,1,2).
Isaac
8

Jeśli jesteś w środku bash, możesz powiedzieć:

bash-3.2$ var=abcd
bash-3.2$ echo ${var:0:2}
ab

To może być właśnie to, czego potrzebujesz…

Dominic Mitchell
źródło
to najłatwiejsza i najprostsza odpowiedź! działał jak urok
aloha
8

Po prostu grep:

echo 'abcdef' | grep -Po "^.."        # ab
Amir Mehler
źródło
Pasuje do moich potrzeb. Możesz usunąć tę -Popcję, aby ją skrócić. Wszystkie wyrażenia regularne będą rozumieć ten wzorzec.
datashaman
6

Możesz użyć printf:

$ original='USCAGoleta9311734.5021-120.1287855805'
$ printf '%-.2s' "$original"
US
bschlueter
źródło
5

colrm - usuwa kolumny z pliku

Aby zostawić pierwsze dwa znaki, po prostu usuń kolumny zaczynające się od 3

cat file | colrm 3
Ian Yang
źródło
4

Dość późno, ale oto jest

sed 's/.//3g'

Lub

awk NF=1 FPAT=..

Lub

perl -pe '$_=unpack a2'
Steven Penny
źródło
2

Jeśli chcesz używać skryptów powłoki i nie polegać na rozszerzeniach innych niż posix (takich jak tak zwane bashizmy), możesz użyć technik, które nie wymagają rozwidlania zewnętrznych narzędzi, takich jak grep, sed, cut, awk itp., sprawić, że twój skrypt będzie mniej wydajny. Może wydajność i przenośność Posix nie są ważne w twoim przypadku użycia. Ale jeśli tak jest (lub po prostu jest to dobry nawyk), możesz użyć następującej metody opcji rozwijania parametrów , aby wyodrębnić pierwsze dwa znaki zmiennej powłoki:

$ sh -c 'var=abcde; echo "${var%${var#??}}"'
ab

Wykorzystuje rozwinięcie parametrów „najmniejszy prefiks” w celu usunięcia pierwszych dwóch znaków (to jest ${var#??}część), a następnie rozwinięcie parametrów „najmniejszy sufiks” ( ${var%część) w celu usunięcia tego ciągu składającego się wyłącznie z dwóch pierwszych znaków z oryginału wartość.

Ta metoda została wcześniej opisana w odpowiedzi na pytanie „Powłoka = Sprawdź, czy zmienna zaczyna się od #”. Ta odpowiedź opisuje również kilka podobnych metod rozwijania parametrów, których można użyć w nieco innym kontekście niż ten, który odnosi się do pierwotnego pytania.

Juan
źródło
Najlepsza odpowiedź powinna być na górze. bez widelców, bez bashizmów. działa nawet z małymi muszlami, takimi jak myślnik.
exore
1

Jeśli twój system używa innej powłoki (nie bash), ale twój system ma bash, możesz nadal używać nieodłącznej manipulacji ciągiem bash, wywołując bashzmienną:

strEcho='echo ${str:0:2}' # '${str:2}' if you want to skip the first two characters and keep the rest
bash -c "str=\"$strFull\";$strEcho;"
palswim
źródło
Używa tej samej metody, co główna odpowiedź , wywołując tylko bashwtedy, gdy jeszcze jej nie używasz.
palswim
Niestety, wiąże się to z całym narzutem wywołania innego procesu, ale czasami ten narzut nie ma tak dużego znaczenia, jak prostota i znajomość.
palswim
1

Dla zabawy dodam jeszcze kilka, że ​​choć są zbyt skomplikowane i bezużyteczne, nie zostały wymienione:

head -c 2 <( echo 'USCAGoleta9311734.5021-120.1287855805')

echo 'USCAGoleta9311734.5021-120.1287855805' | dd bs=2 count=1 status=none

sed -e 's/^\(.\{2\}\).*/\1/;' <( echo 'USCAGoleta9311734.5021-120.1287855805')

cut -c 1-2 <( echo 'USCAGoleta9311734.5021-120.1287855805')

python -c "print(r'USCAGoleta9311734.5021-120.1287855805'[0:2])"

ruby -e 'puts "USCAGoleta9311734.5021-120.1287855805"[0..1]'
Matias Barrios
źródło
0
perl -ple 's/^(..).*/$1/'
dsm
źródło
Zapomniałeś powtórzyć w tym napis.
Chas. Owens
0

jeśli mystring = USCAGoleta9311734.5021-120.1287855805

print substr(mystring,0,2)

wydrukowałoby US

gdzie 0 to pozycja początkowa, a 2 to jak odczytać wiele znaków

Jambobond
źródło
Powiedz ... czy to nie GW-BASIC? Och, czekaj, to jest awk. Przepraszam, na początku nie mogłem powiedzieć.
Wstrzymano do odwołania.
0

Czy to jest to, czego szukasz?

my $string = 'USCAGoleta9311734.5021-120.1287855805';

my $first_two_chars = substr $string, 0, 2;

ref: substr

draegtun
źródło
1
biorąc pod uwagę, że prawdopodobnie będzie to wywoływał z muszli, lepszą formą byłobyperl -e 'print substr $ARGV[0], 0, 2' 'USCAGoleta9311734.5021-120.1287855805'
Chas. Owens