Saya menulis naskah yang perlu menghitung jumlah karakter dalam output perintah dalam satu langkah .
Misalnya, menggunakan perintah readlink -f /etc/fstab
harus kembali 10
karena output dari perintah itu adalah 10 karakter.
Ini sudah dimungkinkan dengan variabel tersimpan menggunakan kode berikut:
variable="somestring";
echo ${#variable};
# 10
Sayangnya, menggunakan rumus yang sama dengan string yang dihasilkan perintah tidak berfungsi:
${#(readlink -f /etc/fstab)};
# bash: ${#(readlink -f /etc/fstab)}: bad substitution
Saya mengerti adalah mungkin untuk melakukan ini dengan terlebih dahulu menyimpan output ke variabel:
variable=$(readlink -f /etc/fstab);
echo ${#variable};
Tapi saya ingin menghapus langkah ekstra.
Apakah ini mungkin? Lebih cocok dengan shell Almquist (sh) yang hanya menggunakan utilitas bawaan atau standar.
readlink -f /etc/fstab
adalah 11 karakter. Jangan lupa baris baru. Kalau tidak, Anda akan melihat/etc/fstabluser@cern:~$
ketika Anda menjalankannya dari shell.Jawaban:
Dengan GNU expr :
The
+
ada fitur khusus GNUexpr
untuk memastikan argumen berikutnya diperlakukan sebagai string bahkan jika hal itu terjadi untuk menjadiexpr
Operator sepertimatch
,length
,+
...Di atas akan menghapus setiap baris baru dari output. Untuk mengatasinya:
Hasilnya dikurangi menjadi 2 karena baris baru terakhir
readlink
dan karakter yang.
kami tambahkan.Dengan string Unicode,
expr
tampaknya tidak berfungsi, karena mengembalikan panjang string dalam byte alih-alih jumlah karakter (Lihat baris 654 )Jadi, Anda bisa menggunakan:
POSIXLY:
Ruang sebelum substitusi perintah mencegah perintah dari crash dengan string start with
-
, jadi kita perlu mengurangi 3.sumber
LC_ALL=C.UTF-8
, yang secara signifikan menyederhanakan hal-hal jika pengkodean string tidak akan diketahui sebelumnya.expr length $(echo "*")
- tidak. Setidaknya menggunakan tanda kutip ganda:expr length "$(…)"
. Tapi ini menghilangkan baris baru dari perintah, ini adalah fitur substitusi perintah yang tidak bisa dihindari. (Anda dapat mengatasinya, tetapi kemudian jawabannya menjadi lebih kompleks.)Tidak yakin bagaimana melakukan ini dengan shell bawaan ( meskipun Gnouc ) tetapi alat standar dapat membantu:
Anda dapat menggunakan
wc -m
karakter mana yang diperhitungkan. Sayangnya, ini juga menghitung baris terakhir sehingga Anda harus menyingkirkannya terlebih dahulu:Anda tentu saja dapat menggunakan
awk
Atau Perl
sumber
expr
adalah built-in? Di shell yang mana?Saya biasanya melakukannya seperti ini:
Untuk melakukan perintah, saya akan menyesuaikannya seperti ini:
Pendekatan ini mirip dengan apa yang Anda lakukan dalam 2 langkah, kecuali kami menggabungkannya menjadi satu baris.
sumber
-m
sebagai gantinya-c
. Dengan karakter unicode, pendekatan Anda akan rusak.readlink -f /etc/fstab | wc -m
?${#variable}
? Setidaknya menggunakan tanda kutip gandaecho -n "$variable"
, tetapi ini masih gagal jika misalnya nilaivariable
adalah-e
. Saat Anda menggunakannya dalam kombinasi dengan substitusi perintah, perlu diingat bahwa trailing baris baru dilucuti.Anda dapat memanggil utilitas eksternal (lihat jawaban lain), tetapi mereka akan membuat skrip Anda lebih lambat, dan sulit untuk memperbaiki saluran air.
Zsh
Di zsh, Anda bisa menulis
${#$(readlink -f /etc/fstab)}
untuk mendapatkan panjang substitusi perintah. Perhatikan bahwa ini bukan panjang dari output perintah, itu adalah panjang dari output tanpa baris baru.Jika Anda ingin panjang pasti dari output, output karakter non-newline tambahan di akhir, dan kurangi satu.
Jika yang Anda inginkan adalah payload di output perintah, maka Anda perlu mengurangi dua di sini, karena outputnya
readlink -f
adalah jalur kanonik ditambah baris baru.Ini berbeda dari
${#$(readlink -f /etc/fstab)}
kasus yang jarang tetapi mungkin terjadi di mana jalur kanonik itu sendiri berakhir di baris baru.Untuk contoh khusus ini, Anda tidak memerlukan utilitas eksternal sama sekali, karena zsh memiliki konstruksi bawaan yang setara dengan
readlink -f
, melalui pengubah riwayatA
.Untuk mendapatkan panjangnya, gunakan pengubah riwayat dalam ekspansi parameter:
Jika Anda memiliki nama file dalam variabel
filename
, itu akan menjadi${#filename:A}
.Kerang Bourne / gaya POSIX
Tak satu pun dari shell Bourne / POSIX murni (Bourne, ash, mksh, ksh93, bash, yash ...) memiliki ekstensi serupa yang saya ketahui. Jika Anda perlu menerapkan substitusi parameter ke output substitusi perintah atau ke substitusi parameter sarang, gunakan tahapan berturut-turut.
Anda dapat memasukkan pemrosesan ke dalam fungsi jika diinginkan.
atau
tetapi biasanya tidak ada manfaatnya; kecuali dengan ksh93, yang menyebabkan garpu tambahan untuk dapat menggunakan output dari fungsi, sehingga membuat skrip Anda lebih lambat, dan jarang ada manfaat keterbacaan.
Sekali lagi, output dari
readlink -f
adalah jalur kanonik ditambah baris baru; jika Anda ingin panjang jalur kanonik, kurangi 2 bukannya 1 incommand_output_length
. Menggunakancommand_output_length_sans_trailing_newlines
memberikan hasil yang benar hanya ketika jalur kanonik itu sendiri tidak berakhir di baris baru.Bytes vs karakter
${#…}
seharusnya menjadi panjang dalam karakter, bukan dalam byte, yang membuat perbedaan dalam lokal multibyte. Versi terbaru dari ksh93, bash, dan zsh menghitung panjang karakter sesuai dengan nilaiLC_CTYPE
pada saat${#…}
konstruk diperluas. Banyak shell umum lainnya tidak benar-benar mendukung local multibyte: pada dash 0.5.7, mksh 46 dan posh 0.12.3,${#…}
mengembalikan panjang dalam byte. Jika Anda ingin panjang karakter dengan cara yang dapat diandalkan, gunakanwc
utilitas:Selama
$LC_CTYPE
menetapkan lokal yang valid, Anda dapat yakin bahwa ini akan salah (pada platform kuno atau terbatas yang tidak mendukung lokal multibyte) atau mengembalikan panjang karakter yang benar. (Untuk Unicode, "panjang karakter" berarti jumlah titik kode - jumlah mesin terbang adalah cerita lain, karena komplikasi seperti menggabungkan karakter.)Jika Anda ingin panjang dalam byte, atur
LC_CTYPE=C
sementara, atau gunakanwc -c
sebagai gantiwc -m
.Menghitung byte atau karakter dengan
wc
menyertakan baris baru yang tertinggal dari perintah. Jika Anda ingin panjang jalur kanonik dalam byte, ituUntuk mendapatkannya dalam karakter, kurangi 2.
sumber
echo .
menambahkan dua karakter, tetapi karakter kedua adalah baris tambahan yang dilucuti oleh substitusi perintah.readlink
output, ditambah.
denganecho
. Kami berdua setuju bahwaecho .
menambahkan dua karakter tetapi baris baru yang tertinggal dihapus. Coba denganprintf .
atau lihat jawaban saya unix.stackexchange.com/a/160499/38906 .readlink
adalah target tautan ditambah baris baru.Ini berfungsi
dash
tetapi itu mengharuskan var yang ditargetkan pasti kosong atau tidak disetel. Itulah sebabnya ini sebenarnya dua perintah - saya secara eksplisit mengosongkan$l
yang pertama:KELUARAN
Itu semua shell builtin - tidak termasuk
readlink
tentu saja - tetapi mengevaluasinya dalam shell saat ini dengan cara itu menyiratkan bahwa Anda harus melakukan tugas sebelum mendapatkan len, itulah sebabnya saya%.s
melihat argumen pertama dalamprintf
string format dan hanya menambahkannya lagi untuk nilai literal pada bagian akhirprintf
daftar arg.Dengan
eval
:KELUARAN
Anda bisa mendekati hal yang sama, tetapi alih-alih output dalam variabel pada perintah pertama Anda mendapatkannya di stdout:
... yang menulis ...
... untuk mengajukan deskriptor 1 tanpa memberikan nilai apa pun ke vars apa pun di shell saat ini.
sumber
variable=$(readlink -f /etc/fstab); echo ${#variable};
Tapi saya ingin menghapus langkah ekstra."expr
, misalnya. Ini mungkin hanya masalah jika entah bagaimana mendapatkan len menyumbat nilai, yang saya akui saya mengalami kesulitan memahami mengapa itu mungkin terjadi, tetapi saya curiga mungkin ada kasus di mana itu penting.eval
way, by the way, mungkin terbersih di sini - itu memberikan output dan len ke nama var yang sama di eksekusi tunggal - sangat dekat dengan melakukanl=length(l):out(l)
. Ngomong-ngomong, melakukanexpr length $(command)
melakukan oklusi nilai yang mendukung len.