<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                # 數值運算 [TOC=2,3] ## 前言 從本文開始,打算結合平時積累和進一步實踐,通過一些范例來介紹Shell編程。因為范例往往能夠給人以學有所用的感覺,而且給人以動手實踐的機會,從而激發人的學習熱情。 考慮到易讀性,這些范例將非常簡單,但是實用,希望它們能夠成為我們解決日常問題的參照物或者是“茶余飯后”的小點心,當然這些“點心”肯定還有值得探討、優化的地方。 更復雜有趣的例子請參考 [Advanced Bash-Scripting Guide](http://www.tldp.org/LDP/abs/html/) (一本深入學習 Shell 腳本藝術的書籍)。 該系列概要: - 目的:享受用 Shell 解決問題的樂趣;和朋友們一起交流和探討。 - 計劃:先零散地寫些東西,之后再不斷補充,最后整理成冊。 - 讀者:熟悉 Linux 基本知識,如文件系統結構、常用命令行工具、Shell 編程基礎等。 - 建議:看范例時,可參考[《Shell基礎十二篇》](http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=2198159)和[《Shell十三問》](http://bbs.chinaunix.net/thread-218853-1-1.html)。 - 環境:如沒特別說明,該系列使用的 Shell 將特指 Bash,版本在 3.1.17 以上。 - 說明:該系列不是依據 Shell 語法組織,而是面向某些潛在的操作對象和操作本身,它們反應了現實應用。當然,在這個過程中肯定會涉及到 Shell 的語法。 這一篇打算討論一下 Shell 編程中的基本數值運算,這類運算包括: - 數值(包括整數和浮點數)間的加、減、乘、除、求冪、求模等 - 產生指定范圍的隨機數 - 產生指定范圍的數列 Shell 本身可以做整數運算,復雜一些的運算要通過外部命令實現,比如 `expr`,`bc`,`awk` 等。另外,可通過 `RANDOM` 環境變量產生一個從 0 到 32767 的隨機數,一些外部工具,比如 `awk` 可以通過 `rand()` 函數產生隨機數。而 `seq` 命令可以用來產生一個數列。下面對它們分別進行介紹。 ## 整數運算 ### 范例:對某個數加 1 ~~~ $ i=0; $ ((i++)) $ echo $i 1 $ let i++ $ echo $i 2 $ expr $i + 1 3 $ echo $i 2 $ echo $i 1 | awk '{printf $1+$2}' 3 ~~~ 說明: `expr` 之后的 `$i`,`+`,1 之間有空格分開。如果進行乘法運算,需要對運算符進行轉義,否則 Shell 會把乘號解釋為通配符,導致語法錯誤; `awk` 后面的 `$1` 和 `$2` 分別指 `$i` 和 1,即從左往右的第 1 個和第 2 個數。 用 Shell 的內置命令查看各個命令的類型如下: ~~~ $ type type type is a shell builtin $ type let let is a shell builtin $ type expr expr is hashed (/usr/bin/expr) $ type bc bc is hashed (/usr/bin/bc) $ type awk awk is /usr/bin/awk ~~~ 從上述演示可看出: `let` 是 Shell 內置命令,其他幾個是外部命令,都在 `/usr/bin` 目錄下。而 `expr` 和 `bc` 因為剛用過,已經加載在內存的 `hash` 表中。這將有利于我們理解在上一章介紹的腳本多種執行方法背后的原理。 說明:如果要查看不同命令的幫助,對于 `let` 和 `type` 等 Shell 內置命令,可以通過 Shell 的一個內置命令 `help` 來查看相關幫助,而一些外部命令可以通過 Shell 的一個外部命令 `man` 來查看幫助,用法諸如 `help let`,`man expr` 等。 ### 范例:從 1 加到某個數 ~~~ #!/bin/bash # calc.sh i=0; while [ $i -lt 10000 ] do ((i++)) done echo $i ~~~ 說明:這里通過 `while [ 條件表達式 ]; do .... done` 循環來實現。`-lt` 是小于號 `<`,具體見 `test` 命令的用法:`man test`。 如何執行該腳本? 辦法一:直接把腳本文件當成子 Shell (Bash)的一個參數傳入 ~~~ $ bash calc.sh $ type bash bash is hashed (/bin/bash) ~~~ 辦法二:是通過 `bash` 的內置命令 `.` 或 `source` 執行 ~~~ $ . ./calc.sh ~~~ 或 ~~~ $ source ./calc.sh $ type . . is a shell builtin $ type source source is a shell builtin ~~~ 辦法三:是修改文件為可執行,直接在當前 Shell 下執行 ~~~ $ chmod ./calc.sh $ ./calc.sh ~~~ 下面,逐一演示用其他方法計算變量加一,即把 `((i++))` 行替換成下面的某一個: ~~~ let i++; i=$(expr $i + 1) i=$(echo $i+1|bc) i=$(echo "$i 1" | awk '{printf $1+$2;}') ~~~ 比較計算時間如下: ~~~ $ time calc.sh 10000 real 0m1.319s user 0m1.056s sys 0m0.036s $ time calc_let.sh 10000 real 0m1.426s user 0m1.176s sys 0m0.032s $ time calc_expr.sh 1000 real 0m27.425s user 0m5.060s sys 0m14.177s $ time calc_bc.sh 1000 real 0m56.576s user 0m9.353s sys 0m24.618s $ time ./calc_awk.sh 100 real 0m11.672s user 0m2.604s sys 0m2.660s ~~~ 說明: `time` 命令可以用來統計命令執行時間,這部分時間包括總的運行時間,用戶空間執行時間,內核空間執行時間,它通過 `ptrace` 系統調用實現。 通過上述比較可以發現 `(())` 的運算效率最高。而 `let` 作為 Shell 內置命令,效率也很高,但是 `expr`,`bc`,`awk` 的計算效率就比較低。所以,在 Shell 本身能夠完成相關工作的情況下,建議優先使用 Shell 本身提供的功能。但是 Shell 本身無法完成的功能,比如浮點運算,所以就需要外部命令的幫助。另外,考慮到 Shell 腳本的可移植性,在性能不是很關鍵的情況下,不要使用某些 Shell 特有的語法。 `let`,`expr`,`bc` 都可以用來求模,運算符都是 `%`,而 `let` 和 `bc` 可以用來求冪,運算符不一樣,前者是 `**`,后者是 `^` 。例如: ### 范例:求模 ~~~ $ expr 5 % 2 1 $ let i=5%2 $ echo $i 1 $ echo 5 % 2 | bc 1 $ ((i=5%2)) $ echo $i 1 ~~~ ### 范例:求冪 ~~~ $ let i=5**2 $ echo $i 25 $ ((i=5**2)) $ echo $i 25 $ echo "5^2" | bc 25 ~~~ ### 范例:進制轉換 進制轉換也是比較常用的操作,可以用 `Bash` 的內置支持也可以用 `bc` 來完成,例如把 8 進制的 11 轉換為 10 進制,則可以: ~~~ $ echo "obase=10;ibase=8;11" | bc -l 9 $ echo $((8#11)) 9 ~~~ 上面都是把某個進制的數轉換為 10 進制的,如果要進行任意進制之間的轉換還是 `bc` 比較靈活,因為它可以直接用 `ibase` 和 `obase` 分別指定進制源和進制轉換目標。 ### 范例:ascii 字符編碼 如果要把某些字符串以特定的進制表示,可以用 `od` 命令,例如默認的分隔符 `IFS` 包括空格、 `TAB` 以及換行,可以用 `man ascii` 佐證。 ~~~ $ echo -n "$IFS" | od -c 0000000 t n 0000003 $ echo -n "$IFS" | od -b 0000000 040 011 012 0000003 ~~~ ## 浮點運算 `let` 和 `expr` 都無法進行浮點運算,但是 `bc` 和 `awk` 可以。 ### 范例:求 1 除以 13,保留 3 位有效數字 ~~~ $ echo "scale=3; 1/13" | bc .076 $ echo "1 13" | awk '{printf("%0.3fn",$1/$2)}' 0.077 ~~~ 說明: `bc` 在進行浮點運算時需指定精度,否則默認為 0,即進行浮點運算時,默認結果只保留整數。而 `awk` 在控制小數位數時非常靈活,僅僅通過 `printf` 的格式控制就可以實現。 補充:在用 `bc` 進行運算時,如果不用 `scale` 指定精度,而在 `bc` 后加上 `-l` 選項,也可以進行浮點運算,只不過這時的默認精度是 20 位。例如: ~~~ $ echo 1/13100 | bc -l .00007633587786259541 ~~~ ### 范例:余弦值轉角度 用 `bc -l` 計算,可以獲得高精度: ~~~ $ export cos=0.996293; echo "scale=100; a(sqrt(1-$cos^2)/$cos)*180/(a(1)*4)" | bc -l 4.934954755411383632719834036931840605159706398655243875372764917732 5495504159766011527078286004072131 ~~~ 當然也可以用 `awk` 來計算: ~~~ $ echo 0.996293 | awk '{ printf("%s\n", atan2(sqrt(1-$1^2),$1)*180/3.1415926535);}' 4.93495 ~~~ ### 范例:有一組數據,求人均月收入最高家庭 在這里隨機產生了一組測試數據,文件名為 `income.txt`。 ~~~ 1 3 4490 2 5 3896 3 4 3112 4 4 4716 5 4 4578 6 6 5399 7 3 5089 8 6 3029 9 4 6195 10 5 5145 ~~~ 說明:上面的三列數據分別是家庭編號、家庭人數、家庭月總收入。 分析:為了求月均收入最高家庭,需要對后面兩列數進行除法運算,即求出每個家庭的月均收入,然后按照月均收入排序,找出收入最高家庭。 實現: ~~~ #!/bin/bash # gettopfamily.sh [ $# -lt 1 ] && echo "please input the income file" && exit -1 [ ! -f $1 ] && echo "$1 is not a file" && exit -1 income=$1 awk '{ printf("%d %0.2fn", $1, $3/$2); }' $income | sort -k 2 -n -r ~~~ 說明: - `[ $# -lt 1 ]`:要求至少輸入一個參數,`$#` 是 Shell 中傳入參數的個數 - `[ ! -f $1 ]`:要求輸入參數是一個文件,`-f` 的用法見 `test` 命令,`man test` - `income=$1`:把輸入參數賦給 income 變量,再作為 `awk` 的參數,即需處理的文件 - `awk`:用文件第三列除以第二列,求出月均收入,考慮到精確性,保留了兩位精度 - `sort -k 2 -n -r`:這里對結果的 `awk` 結果的第二列 `-k 2`,即月均收入進行排序,按照數字排序 `-n`,并按照遞減的順序排序 `-r`。 演示: ~~~ $ ./gettopfamily.sh income.txt 7 1696.33 9 1548.75 1 1496.67 4 1179.00 5 1144.50 10 1029.00 6 899.83 2 779.20 3 778.00 8 504.83 ~~~ 補充:之前的 `income.txt` 數據是隨機產生的。在做一些實驗時,往往需要隨機產生一些數據,在下一小節,我們將詳細介紹它。這里是產生 `income.txt` 數據的腳本: ~~~ #!/bin/bash # genrandomdata.sh for i in $(seq 1 10) do echo $i $(($RANDOM/8192+3)) $((RANDOM/10+3000)) done ~~~ 說明:上述腳本中還用到`seq`命令產生從1到10的一列數,這個命令的詳細用法在該篇最后一節也會進一步介紹。 ## 隨機數 環境變量 `RANDOM` 產生從 0 到 32767 的隨機數,而 `awk` 的 `rand()` 函數可以產生 0 到 1 之間的隨機數。 ### 范例:獲取一個隨機數 ~~~ $ echo $RANDOM 81 $ echo "" | awk '{srand(); printf("%f", rand());}' 0.237788 ~~~ 說明: `srand()` 在無參數時,采用當前時間作為 `rand()` 隨機數產生器的一個 `seed` 。 ### 范例:隨機產生一個從 0 到 255 之間的數字 可以通過 `RANDOM` 變量的縮放和 `awk` 中 `rand()` 的放大來實現。 ~~~ $ expr $RANDOM / 128 $ echo "" | awk '{srand(); printf("%d\n", rand()*255);}' ~~~ 思考:如果要隨機產生某個 IP 段的 IP 地址,該如何做呢?看例子:友善地獲取一個可用的 IP 地址。 ~~~ #!/bin/bash # getip.sh -- get an usable ipaddress automatically # author: falcon &lt;zhangjinw@gmail.com> # update: Tue Oct 30 23:46:17 CST 2007 # set your own network, default gateway, and the time out of "ping" command net="192.168.1" default_gateway="192.168.1.1" over_time=2 # check the current ipaddress ping -c 1 $default_gateway -W $over_time [ $? -eq 0 ] && echo "the current ipaddress is okey!" && exit -1; while :; do # clear the current configuration ifconfig eth0 down # configure the ip address of the eth0 ifconfig eth0 \ $net.$(($RANDOM /130 +2)) \ up # configure the default gateway route add default gw $default_gateway # check the new configuration ping -c 1 $default_gateway -W $over_time # if work, finish [ $? -eq 0 ] && break done ~~~ 說明:如果你的默認網關地址不是 `192.168.1.1`,請自行配置 `default_gateway`(可以用 `route -n` 命令查看),因為用 `ifconfig` 配置地址時不能配置為網關地址,否則你的IP地址將和網關一樣,導致整個網絡不能正常工作。 ## 其他運算 其實通過一個循環就可以產生一系列數,但是有相關工具為什么不用呢!`seq` 就是這么一個小工具,它可以產生一系列數,你可以指定數的遞增間隔,也可以指定相鄰兩個數之間的分割符。 ### 范例:獲取一系列數 ~~~ $ seq 5 1 2 3 4 5 $ seq 1 5 1 2 3 4 5 $ seq 1 2 5 1 3 5 $ seq -s: 1 2 5 1:3:5 $ seq 1 2 14 1 3 5 7 9 11 13 $ seq -w 1 2 14 01 03 05 07 09 11 13 $ seq -s: -w 1 2 14 01:03:05:07:09:11:13 $ seq -f "0x%g" 1 5 0x1 0x2 0x3 0x4 0x5 ~~~ 一個比較典型的使用 `seq` 的例子,構造一些特定格式的鏈接,然后用 `wget` 下載這些內容: ~~~ $ for i in `seq -f"http://thns.tsinghua.edu.cn/thnsebooks/ebook73/%02g.pdf" 1 21`;do wget -c $i; done ~~~ 或者 ~~~ $ for i in `seq -w 1 21`;do wget -c "http://thns.tsinghua.edu.cn/thnsebooks/ebook73/$i"; done ~~~ 補充:在 `Bash` 版本 3 以上,在 `for` 循環的 `in` 后面,可以直接通過 `{1..5}` 更簡潔地產生自 1 到 5 的數字(注意,1 和 5 之間只有兩個點),例如: ~~~ $ for i in {1..5}; do echo -n "$i "; done 1 2 3 4 5 ~~~ ### 范例:統計字符串中各單詞出現次數 我們先給單詞一個定義:由字母組成的單個或者多個字符系列。 首先,統計每個單詞出現的次數: ~~~ $ wget -c http://tinylab.org $ cat index.html | sed -e "s/[^a-zA-Z]/\n/g" | grep -v ^$ | sort | uniq -c ~~~ 接著,統計出現頻率最高的前10個單詞: ~~~ $ wget -c http://tinylab.org $ cat index.html | sed -e "s/[^a-zA-Z]/\n/g" | grep -v ^$ | sort | uniq -c | sort -n -k 1 -r | head -10 524 a 238 tag 205 href 201 class 193 http 189 org 175 tinylab 174 www 146 div 128 title ~~~ 說明: - `cat index.html`: 輸出 index.html 文件里的內容 - `sed -e "s/[^a-zA-Z]/\n/g"`: 把非字母字符替換成空格,只保留字母字符 - `grep -v ^$`: 去掉空行 - `sort`: 排序 - `uniq -c`:統計相同行的個數,即每個單詞的個數 - `sort -n -k 1 -r`:按照第一列 `-k 1` 的數字 `-n` 逆序 `-r` 排序 - `head -10`:取出前十行 ### 范例:統計指定單詞出現次數 可以考慮采取兩種辦法: - 只統計那些需要統計的單詞 - 用上面的算法把所有單詞的個數都統計出來,然后再返回那些需要統計的單詞給用戶 不過,這兩種辦法都可以通過下面的結構來實現。先看辦法一: ~~~ #!/bin/bash # statistic_words.sh if [ $# -lt 1 ]; then echo "Usage: basename $0 FILE WORDS ...." exit -1 fi FILE=$1 ((WORDS_NUM=$#-1)) for n in $(seq $WORDS_NUM) do shift cat $FILE | sed -e "s/[^a-zA-Z]/\n/g" \ | grep -v ^$ | sort | grep ^$1$ | uniq -c done ~~~ 說明: - `if 條件部分`:要求至少兩個參數,第一個單詞文件,之后參數為要統計的單詞 - `FILE=$1`: 獲取文件名,即腳本之后的第一個字符串 - `((WORDS_NUM=$#-1))`:獲取單詞個數,即總的參數個數 `$#` 減去文件名參數(1個) - `for 循環部分`:首先通過 `seq` 產生需要統計的單詞個數系列,`shift` 是 Shell 內置變量(請通過 `help shift` 獲取幫助),它把用戶從命令行中傳入的參數依次往后移動位置,并把當前參數作為第一個參數即 `$1`,這樣通過 `$1`就可以遍歷用戶所有輸入的單詞(仔細一想,這里貌似有數組下標的味道)。你可以考慮把 `shift` 之后的那句替換成 `echo $1` 測試 `shift` 的用法 演示: ~~~ $ chmod +x statistic_words.sh $ ./statistic_words.sh index.html tinylab linux python 175 tinylab 43 linux 3 python ~~~ 再看辦法二,我們只需要修改 `shift` 之后的那句即可: ~~~ #!/bin/bash # statistic_words.sh if [ $# -lt 1 ]; then echo "ERROR: you should input 2 words at least"; echo "Usage: basename $0 FILE WORDS ...." exit -1 fi FILE=$1 ((WORDS_NUM=$#-1)) for n in $(seq $WORDS_NUM) do shift cat $FILE | sed -e "s/[^a-zA-Z]/\n/g" \ | grep -v ^$ | sort | uniq -c | grep " $1$" done ~~~ 演示: ~~~ $ ./statistic_words.sh index.html tinylab linux python 175 tinylab 43 linux 3 python ~~~ 說明:很明顯,辦法一的效率要高很多,因為它提前找出了需要統計的單詞,然后再統計,而后者則不然。實際上,如果使用 `grep` 的 `-E` 選項,我們無須引入循環,而用一條命令就可以搞定: ~~~ $ cat index.html | sed -e "s/[^a-zA-Z]/\n/g" | grep -v ^$ | sort | grep -E "^tinylab$|^linux$" | uniq -c 43 linux 175 tinylab ~~~ 或者 ~~~ $ cat index.html | sed -e "s/[^a-zA-Z]/\n/g" | grep -v ^$ | sort | egrep "^tinylab$|^linux$" | uniq -c 43 linux 175 tinylab ~~~ 說明:需要注意到 `sed` 命令可以直接處理文件,而無需通過 `cat` 命令輸出以后再通過管道傳遞,這樣可以減少一個不必要的管道操作,所以上述命令可以簡化為: ~~~ $ sed -e "s/[^a-zA-Z]/\n/g" index.html | grep -v ^$ | sort | egrep "^tinylab$|^linux$" | uniq -c 43 linux 175 tinylab ~~~ 所以,可見這些命令 `sed`,`grep`,`uniq`,`sort` 是多么有用,它們本身雖然只完成簡單的功能,但是通過一定的組合,就可以實現各種五花八門的事情啦。對了,統計單詞還有個非常有用的命令 `wc -w`,需要用到的時候也可以用它。 補充:在 [Advanced Bash-Scripting Guide](http://www.tldp.org/LDP/abs/html/) 一書中還提到 `jot` 命令和 `factor` 命令,由于機器上沒有,所以沒有測試,`factor` 命令可以產生某個數的所有素數。如: ~~~ $ factor 100 100: 2 2 5 5 ~~~ ## 小結 到這里,Shell 編程范例之數值計算就結束啦。該篇主要介紹了: - Shell 編程中的整數運算、浮點運算、隨機數的產生、數列的產生 - Shell 的內置命令、外部命令的區別,以及如何查看他們的類型和幫助 - Shell 腳本的幾種執行辦法 - 幾個常用的 Shell 外部命令: `sed`,`awk`,`grep`,`uniq`,`sort` 等 - 范例:數字遞增;求月均收入;自動獲取 `IP` 地址;統計單詞個數 - 其他:相關用法如命令列表,條件測試等在上述范例中都已涉及,請認真閱讀之 如果您有時間,請溫習之。 ## 資料 - [Advanced Bash-Scripting Guide](http://www.tldp.org/LDP/abs/html/) - [shell 十三問](http://bbs.chinaunix.net/thread-218853-1-1.html) - [shell 基礎十二篇](http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=2198159) - SED 手冊 - AWK 使用手冊 - 幾個 Shell 討論區 - [LinuxSir.org](http://www.linuxsir.org/bbs/forumdisplay.php?f=60) - [ChinaUnix.net](http://bbs.chinaunix.net/forum-24-1.html) ## 后記 大概花了 3 個多小時才寫完,目前是 23:33,該回宿舍睡覺啦,明天起來修改錯別字和補充一些內容,朋友們晚安! 10 月 31 號,修改部分措辭,增加一篇統計家庭月均收入的范例,添加總結和參考資料,并用附錄所有代碼。 Shell 編程是一件非常有趣的事情,如果您想一想:上面計算家庭月均收入的例子,然后和用 `M$ Excel` 來做這個工作比較,你會發現前者是那么簡單和省事,而且給您以運用自如的感覺。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看