Piszę skrypt, który musi obliczyć liczbę znaków w wynikach polecenia w jednym kroku .
Na przykład użycie polecenia readlink -f /etc/fstab
powinno powrócić, 10
ponieważ 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.
readlink -f /etc/fstab
to 11 znaków. Nie zapomnij nowej linii. W przeciwnym razie zobaczysz,/etc/fstabluser@cern:~$
gdy uruchomisz go z powłoki.Odpowiedzi:
Z GNU expr :
+
Istnieje szczególna cecha GNUexpr
, aby upewnić się, że następnym argumentem jest traktowany jako ciąg znaków, nawet jeśli zdarza się byćexpr
operator jakmatch
,length
,+
...Powyższe spowoduje usunięcie każdej nowej linii wyniku. Aby obejść ten problem:
Wynik został odjęty do 2, ponieważ ostatnia nowa linia
readlink
i znak,.
który dodaliśmy.W przypadku łańcucha Unicode
expr
wydaje się nie działać, ponieważ zwraca liczbę znaków w bajtach zamiast liczby znaków (patrz wiersz 654 )Możesz więc użyć:
POSIXLY:
Spacja przed podstawieniem polecenia zapobiega awarii polecenia z ciągiem zaczynającym się od
-
, więc musimy odjąć 3.źródło
LC_ALL=C.UTF-8
, co znacznie upraszcza rzeczy, jeśli kodowanie ciągu nie będzie wcześniej znane.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.)Nie jestem pewien, jak to zrobić za pomocą wbudowanych powłok ( choć Gnouc ), ale standardowe narzędzia mogą pomóc:
Możesz użyć,
wc -m
który liczy znaki. Niestety, liczy się również ostatnia nowa linia, więc najpierw musisz się jej pozbyć:Możesz oczywiście użyć
awk
Lub Perl
źródło
expr
jest wbudowany? W której skorupceZwykle robię to w ten sposób:
Aby wykonywać polecenia, dostosowałbym to tak:
To podejście jest podobne do tego, co robiłeś w 2 krokach, z tym że łączymy je w jedną linijkę.
źródło
-m
zamiast-c
. W przypadku znaków Unicode twoje podejście zostanie przerwane.readlink -f /etc/fstab | wc -m
?${#variable}
? Przynajmniej używaj podwójnych cudzysłowówecho -n "$variable"
, ale to nadal nie powiedzie się, jeśli np. Wartośćvariable
to-e
. Gdy używasz go w połączeniu z zastępowaniem poleceń, pamiętaj, że końcowe znaki nowej linii są usuwane.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.
Jeśli to, czego chcesz, to ładunek w danych wyjściowych polecenia, musisz odjąć dwa tutaj, ponieważ wynikiem
readlink -f
jest ścieżka kanoniczna plus znak nowej linii.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 -f
poprzez modyfikator historiiA
.Aby uzyskać długość, użyj modyfikatora historii w rozszerzeniu parametru:
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ę.
lub
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 -f
jest ścieżka kanoniczna plus nowy wiersz; jeśli chcesz długość ścieżki kanonicznej, odejmij 2 zamiast 1 calacommand_output_length
. Użyciecommand_output_length_sans_trailing_newlines
daje 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_CTYPE
w 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żyjwc
narzędzia:Tak długo, jak
$LC_CTYPE
okreś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=C
tymczasowo lub użyjwc -c
zamiastwc -m
.Zliczanie bajtów lub znaków za pomocą
wc
obejmuje końcowe znaki nowego wiersza polecenia. Jeśli chcesz długość kanonicznej ścieżki w bajtach, toAby uzyskać go w postaci, odejmij 2.
źródło
echo .
dodaje dwa znaki, ale drugi znak jest końcowym znakiem nowej linii, która jest usuwana przez podstawienie polecenia.readlink
wyjścia, plus.
przezecho
. Oboje zgadzamy się, żeecho .
dodamy dwa znaki, ale końcowy znak nowej linii został usunięty. Spróbujprintf .
lub zobacz moją odpowiedź unix.stackexchange.com/a/160499/38906 .readlink
jest cel łącza plus znak nowej linii.Działa
dash
to, 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$l
w pierwszym:WYNIK
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%.s
wyławiam pierwszy argument wprintf
łańcuchu formatu i dodam go ponownie dla dosłowna wartość na końcuprintf
listy arg.Z
eval
:WYNIK
Możesz zbliżyć się do tej samej rzeczy, ale zamiast wyniku w zmiennej w pierwszym poleceniu otrzymujesz ją na standardowe wyjście:
... który pisze ...
... do deskryptora pliku 1 bez przypisywania żadnej wartości do jakichkolwiek zmiennych w bieżącej powłoce.
źródło
variable=$(readlink -f /etc/fstab); echo ${#variable};
ale chciałbym usunąć dodatkowy krok”.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.eval
mówiąc, sposób jest tutaj prawdopodobnie najczystszy - przypisuje wyjście i len do tej samej nazwy zmiennej w jednym wykonaniu - bardzo blisko do zrobienial=length(l):out(l)
. Robiexpr length $(command)
robi zamykać wartości na korzyść len, nawiasem mówiąc.