
我是 Shell 腳本新手。我希望編寫一個 Unix 腳本來呼叫 C 程序N= 2^{i}
,i= 1,2 ....20
;然後將此數據記錄在文件中。
(程式使用 C 中的梯形規則計算定積分,並傳回每項 N 的迭代結果和誤差。)
當我剛開始學習 C 時,我寫了梯形規則的程式碼:
#include<stdio.h>
#include<math.h>
#define PI 3.14159265358979323846
float fn(float x)
{
float integrand;
integrand = (1.0/(1.0+x*x));
return integrand;
}
int main()
{
int i,N;
float a,b,sum=0,result=0,h;
float error;
printf("Enter the no of equally spaced points =");
scanf("%d",&N);
printf("Enter the lower limit=");
scanf("%f",&a);
printf("Enter the upper limit=");
scanf("%f",&b);
h=(b-a)/(N-1);
for(i=1;i<=N;i++)
{
sum=sum+fn(a+i*h);
result=(fn(a)+fn(b)+2*sum)*h/2;
error = fabs((atan(b)-atan(a))-result);
//error = PI/2.0 - result;
printf("N=%d result=%f error=%f\n", i, result, error);
}
printf("final result =%f\n", result);
printf("cumulative error =%f\n", error);
}
我透過以下方式執行這段程式碼
gcc -o err.o trap_error.c -lm
我的 gcc 版本是gcc (Ubuntu 5.4.0-6ubuntu1~16.04.2) 5.4.0 20160609
我在網上隨機搜索,但沒有找到有用的信息,我想我也必須修改我的代碼。如果你幫我編寫 Unix 腳本,然後將輸出重新導向到檔案中.txt
。帶有解釋的 Unix 腳本將會非常有幫助。
答案1
您不應該提示使用者並從標準輸入讀取參數,而應該接受Unix哲學,並改用命令列參數。
下面的例子有點長,因為我想展示我喜歡的參數檢查函數。scanf()
函數係列不檢查溢出,因此strto*()
需要使用。此外,數字後面有時可能會出現垃圾(例如,「12l」——最後一個是字母 L——而不是「121」),我個人希望捕獲這一點。
#include <stdlib.h>
#include <locale.h>
#include <ctype.h>
#include <stdio.h>
#include <errno.h>
/* Helper function to parse a double.
* Returns 0 if successful, nonzero otherwise.
*/
static int parse_double(const char *s, double *v)
{
const char *end;
double val;
if (!s)
return errno = EINVAL;
end = s;
errno = 0;
val = strtod(s, (char **)&end);
if (errno)
return errno;
if (!end || end == s)
return errno = EINVAL;
while (*end != '\0' && isspace(*end))
end++;
if (*end != '\0')
return errno = EINVAL;
if (v)
*v = val;
return 0;
}
/* Helper function to parse a long.
* Returns 0 if successful, nonzero otherwise.
*/
static int parse_long(const char *s, long *v)
{
const char *end;
long val;
if (!s)
return errno = EINVAL;
end = s;
errno = 0;
val = strtol(s, (char **)&end, 0);
if (errno)
return errno;
if (!end || end == s)
return errno = EINVAL;
while (*end != '\0' && isspace(*end))
end++;
if (*end != '\0')
return errno = EINVAL;
if (v)
*v = val;
return 0;
}
其中,parse_long()
支援十進位(987
)、十六進位(0x3DB
)和八進位(01733
)表示法。
那麼main()
就像是
int main(int argc, char *argv[])
{
double min, max;
long n;
setlocale(LC_ALL, "");
/* Require "command N min max" -- four parameters,
* including the executable file name (argv[0]). */
if (argc != 4 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s N min max\n", argv[0]);
return EXIT_FAILURE;
}
if (parse_long(argv[1], &n) || n < 1L) {
fprintf(stderr, "%s: Invalid N.\n", argv[1]);
return EXIT_FAILURE;
}
if (parse_double(argv[2], &min)) {
fprintf(stderr, "%s: Invalid minimum.\n", argv[2]);
return EXIT_FAILURE;
}
if (parse_double(argv[3], &max)) {
fprintf(stderr, "%s: Invalid maximum.\n", argv[3]);
return EXIT_FAILURE;
}
if (min > max) {
const double tmp = min;
min = max;
max = tmp;
}
/* ... */
return EXIT_SUCCESS;
}
告訴setlocale(LC_ALL, "");
C 庫檢查當前環境,並設定本地化以匹配。該程式僅使用該類別LC_CTYPE
來確定哪些字元是空格(空格或製表符)。儘管如此,這是一個很好的做法:如果在某些時候您希望支援像ä
和 之類的字符€
,您可以切換到寬字符和 I/O。
作為學習者,您可以省略parse_long()
和parse_double()
,並將它們在if
子句中替換為sscanf()
, 並忽略本地化。它可以為您節省幾行程式碼,
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
double min, max;
long n;
/* Require "command N min max" -- four parameters,
* including the executable file name (argv[0]). */
if (argc != 4 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s N min max\n", argv[0]);
return EXIT_FAILURE;
}
if (sscanf(argv[1], " %ld", &n) != 1 || n < 1L) {
fprintf(stderr, "%s: Invalid N.\n", argv[1]);
return EXIT_FAILURE;
}
if (sscanf(argv[2], " %lf", &min) != 1) {
fprintf(stderr, "%s: Invalid minimum.\n", argv[2]);
return EXIT_FAILURE;
}
if (sscanf(argv[3], " %lf", &max) != 1) {
fprintf(stderr, "%s: Invalid maximum.\n", argv[3]);
return EXIT_FAILURE;
}
if (min > max) {
const double tmp = min;
min = max;
max = tmp;
}
/* ... */
return EXIT_SUCCESS;
}
但在我看來,為什麼要學習一種在實踐上不夠充分的方法呢?我個人知道一些愚蠢的假設的情況,例如“人名只包含字母 A 到 Z”花了幾十個小時才解決(計算叢集上的 411 服務,使用者名稱不是英文)。我們生活在一個全球化的世界,你們說英語的人最好已經排隊,放棄你們愚蠢的假設。
人們似乎也無法事後學習在地化。我遇到的大多數「經驗豐富的 C 程式設計師」似乎根本不知道也不關心本地化或字元集問題。 (遠遠超出到處使用 UTF-8)這意味著其他人必須花費大量時間來解決他們的錯誤假設,浪費時間和精力......可恥。
當您的程式採用接受命令列參數的形式時,您可以使用 Bash 循環,例如
for (( i=1; i<=20; i++ )); do ./yourprog $i 0.0 10.0 ; done > output.txt
請注意,如果將資料輸出到空格或製表符分隔的列中,*
或者-
如果列缺少數據,則可以使用gnuplot
它來繪製資料。
例如,如果您output.txt
有
#N result error
1 3.1 0.04159265359
2 3.14 0.00159265359
3 3.141 0.00059265359
等等,您可以使用例如查看數據
gnuplot -p -e 'plot "output.txt" u 2:3 notitle w lines'
Gnuplot 忽略以 a 開頭的行#
,因此您可以在檔案開頭使用此類註解或標題來說明每個欄位的用途。請參閱文件了解更多。我個人更喜歡以SVG
或PDF
格式保存繪圖,這樣它們就是小文件,但具有高品質的向量圖形。這是我特別推薦的課程作業。
答案2
您正在編譯和鏈接,因此輸出將是可執行文件,而不是目標文件,這使得 .o 後綴具有誤導性。
gcc -o err trap_error.c -lm
可能是一個更好的主意。
目前還不清楚您要問什麼,但看起來您正在嘗試為其提供一些自動生成的輸入並將所有輸出重定向到檔案。您可以N= 2^{i}, i= 1,2 ....20;
使用以下命令在 bash 中產生:
for ((i=2;i<2**20;i*=2)); do echo "$i" ; done
如果 -1 和 1 分別是你的下限和上限,那麼你可以在 $i 之後添加它們,並將每個這樣的三元組通過管道傳遞給你的程式的呼叫:
for ((i=2;i<2**20;i*=2)); do echo "$i -1 1" | ./err ; done > out.txt
該 > out.txt
部分會將所有 ./err 呼叫的所有輸出重定向到out.txt
.