Liczba znaków w danych wyjściowych polecenia powłoki


12

Piszę skrypt, który musi obliczyć liczbę znaków w wynikach polecenia w jednym kroku .

Na przykład użycie polecenia readlink -f /etc/fstabpowinno powrócić, 10ponieważ wynik tego polecenia ma długość 10 znaków.

Jest to już możliwe w przypadku przechowywanych zmiennych przy użyciu następującego kodu:

variable="somestring";
echo ${#variable};
# 10

Niestety użycie tej samej formuły z ciągiem generowanym przez polecenie nie działa:

${#(readlink -f /etc/fstab)};
# bash: ${#(readlink -f /etc/fstab)}: bad substitution

Rozumiem, że można to zrobić, najpierw zapisując dane wyjściowe w zmiennej:

variable=$(readlink -f /etc/fstab);
echo ${#variable};

Ale chciałbym usunąć dodatkowy krok.

czy to możliwe? Preferowana jest kompatybilność z powłoką Almquista (sh) przy użyciu tylko wbudowanych lub standardowych narzędzi.


1
Dane wyjściowe readlink -f /etc/fstabto 11 znaków. Nie zapomnij nowej linii. W przeciwnym razie zobaczysz, /etc/fstabluser@cern:~$ gdy uruchomisz go z powłoki.
Phil Frost

@PhilFrost wydajesz się mieć zabawny monit, czy pracujesz w CERN?
Dmitrij Grigoriew

Odpowiedzi:


9

Z GNU expr :

$ expr length + "$(readlink -f /etc/fstab)"
10

+Istnieje szczególna cecha GNU expr, aby upewnić się, że następnym argumentem jest traktowany jako ciąg znaków, nawet jeśli zdarza się być exproperator jak match, length, +...

Powyższe spowoduje usunięcie każdej nowej linii wyniku. Aby obejść ten problem:

$ expr length + "$(readlink -f /etc/fstab; printf .)" - 2
10

Wynik został odjęty do 2, ponieważ ostatnia nowa linia readlinki znak, .który dodaliśmy.

W przypadku łańcucha Unicode exprwydaje się nie działać, ponieważ zwraca liczbę znaków w bajtach zamiast liczby znaków (patrz wiersz 654 )

$ LC_ALL=C.UTF-8 expr length ăaa
4

Możesz więc użyć:

$ printf "ăaa" | LC_ALL=C.UTF-8 wc -m
3

POSIXLY:

$ expr " $(readlink -f /etc/fstab; printf .)" : ".*" - 3
10

Spacja przed podstawieniem polecenia zapobiega awarii polecenia z ciągiem zaczynającym się od -, więc musimy odjąć 3.


Dzięki! Wygląda na to, że twój trzeci przykład działa nawet bez LC_ALL=C.UTF-8, co znacznie upraszcza rzeczy, jeśli kodowanie ciągu nie będzie wcześniej znane.
user339676,

2
expr length $(echo "*")- nie. Przynajmniej używać cudzysłowów: expr length "$(…)". Ale to usuwa końcowe znaki z polecenia, jest nieuniknioną funkcją zastępowania poleceń. (Możesz obejść ten problem, ale odpowiedź staje się jeszcze bardziej złożona.)
SO Gillesa - przestań być zły ”

6

Nie jestem pewien, jak to zrobić za pomocą wbudowanych powłok ( choć Gnouc ), ale standardowe narzędzia mogą pomóc:

  1. Możesz użyć, wc -mktóry liczy znaki. Niestety, liczy się również ostatnia nowa linia, więc najpierw musisz się jej pozbyć:

    readlink -f /etc/fstab | tr -d '\n' | wc -m
  2. Możesz oczywiście użyć awk

    readlink -f /etc/fstab | awk '{print length($0)}'
  3. Lub Perl

    readlink -f /etc/fstab | perl -lne 'print length'

Masz na myśli, że exprjest wbudowany? W której skorupce
mikeserv

5

Zwykle robię to w ten sposób:

$ echo -n "$variable" | wc -m
10

Aby wykonywać polecenia, dostosowałbym to tak:

$ echo -n "$(readlink -f /etc/fstab)" | wc -m
10

To podejście jest podobne do tego, co robiłeś w 2 krokach, z tym że łączymy je w jedną linijkę.


2
Musisz użyć -mzamiast -c. W przypadku znaków Unicode twoje podejście zostanie przerwane.
cuonglm,

1
Dlaczego nie po prostu readlink -f /etc/fstab | wc -m?
Phil Frost

1
Dlaczego zamiast tego używasz tej zawodnej metody ${#variable}? Przynajmniej używaj podwójnych cudzysłowów echo -n "$variable", ale to nadal nie powiedzie się, jeśli np. Wartość variableto -e. Gdy używasz go w połączeniu z zastępowaniem poleceń, pamiętaj, że końcowe znaki nowej linii są usuwane.
Gilles 'SO - przestań być zły'

@ philfrost b / c to, co pokazałem, zbudowało to, co op już myślał. Działa to również w przypadku wszystkich poleceń cmd, które mógł ustawić wcześniej w varach, i chce, aby ich długości były następstwem. Terdon ma już ten przykład.
slm

1

Możesz wywoływać narzędzia zewnętrzne (zobacz inne odpowiedzi), ale spowalniają one twój skrypt i ciężko jest uzyskać prawidłową instalację wodociągową.

Zsh

W zsh możesz pisać, ${#$(readlink -f /etc/fstab)}aby uzyskać długość podstawienia polecenia. Zauważ, że nie jest to długość danych wyjściowych polecenia, to długość danych wyjściowych bez końcowego znaku nowej linii.

Jeśli potrzebujesz dokładnej długości wyniku, wypisz dodatkowy znak nie będący znakiem nowej linii na końcu i odejmij jeden.

$((${#$(readlink -f /etc/fstab; echo .)} - 1))

Jeśli to, czego chcesz, to ładunek w danych wyjściowych polecenia, musisz odjąć dwa tutaj, ponieważ wynikiem readlink -fjest ścieżka kanoniczna plus znak nowej linii.

$((${#$(readlink -f /etc/fstab; echo .)} - 2))

Różni się to od ${#$(readlink -f /etc/fstab)}rzadkiego, ale możliwego przypadku, gdy sama ścieżka kanoniczna kończy się nową linią.

W tym konkretnym przykładzie w ogóle nie potrzebujesz zewnętrznego narzędzia, ponieważ zsh ma wbudowaną konstrukcję równoważną readlink -fpoprzez modyfikator historii A.

echo /etc/fstab(:A)

Aby uzyskać długość, użyj modyfikatora historii w rozszerzeniu parametru:

${#${:-/etc/fstab}:A}

Jeśli masz nazwę pliku w zmiennej filename, byłoby to ${#filename:A}.

Pociski w stylu Bourne / POSIX

Żadna z czystych powłok Bourne / POSIX (Bourne, ash, mksh, ksh93, bash, yash…) nie ma podobnego rozszerzenia, jakie znam. Jeśli chcesz zastosować podstawienie parametru do wyniku podstawienia polecenia lub zagnieżdżić podstawienia parametrów, użyj kolejnych etapów.

Jeśli chcesz, możesz przekształcić przetwarzanie w funkcję.

command_output_length_sans_trailing_newlines () {
  set -- "$("$@")"
  echo "${#1}"
}

lub

command_output_length () {
  set -- "$("$@"; echo .)"
  echo "$((${#1} - 1))"
}

ale zwykle nie ma korzyści; z wyjątkiem ksh93, który powoduje, że dodatkowy widelec może korzystać z danych wyjściowych funkcji, co powoduje spowolnienie skryptu i rzadko ma jakiekolwiek korzyści z czytelności.

Po raz kolejny wyjściem readlink -fjest ścieżka kanoniczna plus nowy wiersz; jeśli chcesz długość ścieżki kanonicznej, odejmij 2 zamiast 1 cala command_output_length. Użycie command_output_length_sans_trailing_newlinesdaje właściwy wynik tylko wtedy, gdy sama ścieżka kanoniczna nie kończy się na nowej linii.

Bajty kontra postacie

${#…}ma mieć długość w znakach, a nie w bajtach, co robi różnicę w ustawieniach wielobajtowych. Racjonalnie aktualne wersje ksh93, bash i zsh obliczają długość w znakach zgodnie z wartością LC_CTYPEw momencie ${#…}rozwijania konstrukcji. Wiele innych popularnych powłok nie obsługuje tak naprawdę wielobajtowych ustawień narodowych: od myślnika 0.5.7, mksh 46 i posh 0.12.3 ${#…}zwraca długość w bajtach. Jeśli chcesz, aby długość w znakach była niezawodna, użyj wcnarzędzia:

$(readlink -f /etc/fstab | wc -m)

Tak długo, jak $LC_CTYPEokreśla prawidłową lokalizację, możesz być pewien, że spowoduje to błąd (na starej lub ograniczonej platformie, która nie obsługuje ustawień wielobajtowych) lub zwróci prawidłową długość znaków. (W przypadku Unicode „długość w znakach” oznacza liczbę punktów kodowych - liczba glifów to kolejna historia, z powodu komplikacji, takich jak łączenie znaków.)

Jeśli chcesz długość w bajtach, ustaw LC_CTYPE=Ctymczasowo lub użyj wc -czamiast wc -m.

Zliczanie bajtów lub znaków za pomocą wcobejmuje końcowe znaki nowego wiersza polecenia. Jeśli chcesz długość kanonicznej ścieżki w bajtach, to

$(($(readlink -f /etc/fstab | wc -c) - 1))

Aby uzyskać go w postaci, odejmij 2.


@cuonglm Nie, musisz odjąć 1. echo .dodaje dwa znaki, ale drugi znak jest końcowym znakiem nowej linii, która jest usuwana przez podstawienie polecenia.
Gilles „SO- przestań być zły”

Znak nowej linii jest od readlinkwyjścia, plus .przez echo. Oboje zgadzamy się, że echo .dodamy dwa znaki, ale końcowy znak nowej linii został usunięty. Spróbuj printf .lub zobacz moją odpowiedź unix.stackexchange.com/a/160499/38906 .
cuonglm,

@cuonglm Pytanie zadało liczbę znaków w danych wyjściowych polecenia. Wynikiem readlinkjest cel łącza plus znak nowej linii.
Gilles „SO- przestań być zły”

0

Działa dashto, ale wymaga, aby docelowy var był zdecydowanie pusty lub rozbrojony. Właśnie dlatego tak naprawdę są to dwa polecenia - ja wyraźnie opróżniam $lw pierwszym:

l=;printf '%.slen is %d and result is %s\n' \
    "${l:=$(readlink -f /etc/fstab)}" "${#l}" "$l"

WYNIK

len is 10 and result is /etc/fstab

To wszystko wbudowane powłoki - oczywiście nie w tym readlink- ale ocena tego w bieżącej powłoce w ten sposób implikuje, że musisz wykonać przypisanie przed uzyskaniem len, dlatego właśnie %.swyławiam pierwszy argument w printfłańcuchu formatu i dodam go ponownie dla dosłowna wartość na końcu printflisty arg.

Z eval:

l=$(readlink -f /etc/fstab) eval 'l=${#l}:$l'
printf %s\\n "$l"

WYNIK

10:/etc/fstab

Możesz zbliżyć się do tej samej rzeczy, ale zamiast wyniku w zmiennej w pierwszym poleceniu otrzymujesz ją na standardowe wyjście:

PS4='${#0}:$0' dash -cx '2>&1' "$(readlink -f /etc/fstab)"

... który pisze ...

10:/etc/fstab

... do deskryptora pliku 1 bez przypisywania żadnej wartości do jakichkolwiek zmiennych w bieżącej powłoce.


1
Czy nie jest to dokładnie to, czego OP chciał uniknąć? „Rozumiem, że można to zrobić, najpierw zapisując dane wyjściowe w zmiennej: variable=$(readlink -f /etc/fstab); echo ${#variable};ale chciałbym usunąć dodatkowy krok”.
terdon

@terdon, prawdopodobnie źle zrozumiałem, ale miałem wrażenie, że średnikiem jest problem, a nie zmienna. Dlatego pobierają len i dane wyjściowe w jednym prostym poleceniu, używając tylko wbudowanych powłok. Na przykład powłoka nie wykonuje readlink, a następnie exec expr. Prawdopodobnie ma to znaczenie tylko , jeśli w jakiś sposób uzyskanie wartości Len pomija wartość, co, przyznaję, mam trudności ze zrozumieniem, dlaczego tak jest, ale podejrzewam, że może istnieć przypadek, w którym ma to znaczenie.
mikeserv

1
Nawiasem evalmówiąc, sposób jest tutaj prawdopodobnie najczystszy - przypisuje wyjście i len do tej samej nazwy zmiennej w jednym wykonaniu - bardzo blisko do zrobienia l=length(l):out(l). Robi expr length $(command) robi zamykać wartości na korzyść len, nawiasem mówiąc.
mikeserv
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.