プログラミングなどなど

プログラミングに関係しそうな内容を

Linux系:manコマンド使ってみよう!

私は、普段の開発はほぼLinuxで行っています。
その際に、「コマンドのオプションなんだっけ?」とか、「関数の引数なんだっけ?」と思うことがあります。

インターネットで調べるのも有りですが、ぱっと調べられるので使えるようになっていると便利です。

manコマンドでmanを調べる

manコマンドでmanについても調べることができます。

$ man man

以下はページャで出力されます。

MAN(1)                             マニュアルページユーティリティー                             MAN(1)

名前
       man - an interface to the system reference manuals

書式
       man [man options] [[section] page ...] ...
       man -k [apropos options] regexp ...
       man -K [man options] [section] term ...
       man -f [whatis options] page ...
       man -l [man options] file ...
       man -w|-W [man options] page ...

説明
       man  is  the  system's manual pager.  Each page argument given to man is normally the name of a
       program, utility or function.  The manual page associated with each of these arguments is  then
       found  and  displayed.  A section, if provided, will direct man to look only in that section of
       the manual.  The default action is to search in all  of  the  available  sections  following  a
       pre-defined order (see DEFAULTS), and to show only the first page found, even if page exists in
       several sections.

       次の表はマニュアルの section 番号およびその section に含まれるページの種類を示します。

       1   実行プログラムまたはシェルコマンド
       2   システムコール (カーネルが提供する関数)
       3   ライブラリー呼び出し (プログラムライブラリーに含まれる関数)
       4   Special files (usually found in /dev)
       5   File formats and conventions, e.g. /etc/passwd
       6   ゲーム
       7   Miscellaneous (including macro packages and conventions), e.g. man(7), groff(7)
       8   システム管理コマンド (通常は root 用)
       9   カーネルルーチン [非標準]

       マニュアルページ page は複数の節で構成されます。
:

私はセクション1,2,3をよく見ます。セクション1を見ていると、よく使っているコマンドでも知らないオプションについて記述があったり、別の機会に使えそうなオプションの記述があったりと発見があります。

調べたい内容が出力されない場合

例えばですが、printfについて調べようとするとセクション1のprintfコマンドの内容が出力されます。

$ man printf
PRINTF(1)                                    User Commands                                   PRINTF(1)

NAME
       printf - format and print data

SYNOPSIS
       printf FORMAT [ARGUMENT]...
       printf OPTION

DESCRIPTION
       Print ARGUMENT(s) according to FORMAT, or execute according to OPTION:
:

printfコマンドについて調べる場合はこれでよいのですが、printf関数について調べたい場合はライブラリーのセクション3を指定して調べます。

$ man 3 printf
PRINTF(3)                              Linux Programmer's Manual                             PRINTF(3)

NAME
       printf, fprintf, dprintf, sprintf, snprintf, vprintf, vfprintf, vdprintf, vsprintf, vsnprintf -
       formatted output conversion

SYNOPSIS
       #include <stdio.h>

       int printf(const char *format, ...);
       int fprintf(FILE *stream, const char *format, ...);
       int dprintf(int fd, const char *format, ...);
       int sprintf(char *str, const char *format, ...);
       int snprintf(char *str, size_t size, const char *format, ...);
:

どのセクションにあるかよくわからない場合は-kオプションが助けになると思います。

$ man -k printf
Printf (3o)          - Formatted output functions.
asprintf (3)         - print to allocated string
dprintf (3)          - formatted output conversion
fprintf (3)          - formatted output conversion
fwprintf (3)         - formatted wide-character output conversion
printf (1)           - format and print data
printf (3)           - formatted output conversion
set_matchpathcon_printf (3) - set flags controlling the operation of matchpathcon or matchpathcon_inde...
snprintf (3)         - formatted output conversion
sprintf (3)          - formatted output conversion
Stdlib.Printf (3o)   - no description
swprintf (3)         - formatted wide-character output conversion
vasprintf (3)        - print to allocated string
vdprintf (3)         - formatted output conversion
vfprintf (3)         - formatted output conversion
vfwprintf (3)        - formatted wide-character output conversion
vprintf (3)          - formatted output conversion
vsnprintf (3)        - formatted output conversion
vsprintf (3)         - formatted output conversion
vswprintf (3)        - formatted wide-character output conversion
vwprintf (3)         - formatted wide-character output conversion
wprintf (3)          - formatted wide-character output conversion
XtAsprintf (3)       - memory management functions

shell:天気情報をWebAPIで取得してみる

Twitter APIを使ってみようと思ったのですが、認証やら何やらが必要になりそうでした。これまでWebAPIは使ったことがなかったので、ひつまず手に届きそうな天気情報を取得するものから試してみました。

お天気Webサービス仕様

手に届きそうなものを探していたらちょうど「お天気Webサービス仕様」(http://weather.livedoor.com/weather_hacks/webservice)というものを見つけました。

横浜の天気情報をリクエストする

説明を読むと同ページからリンクしている「全国の地点定義表(RSS)」に記載されている一次細分区のidを使って天気情報をリクエストできるようでした。
試しに、横浜(140010)を使ってリクエストを投げてみます。

リクエストを送信する
$ GET http://weather.livedoor.com/forecast/webservice/json/v1?city=140010
{"pinpointLocations":[{"link":"http://weather.livedoor.com/area/forecast/1410000","name":"\
:

JSONデータから必要情報を得る

取得した情報から自分が欲しい情報を抽出します。
欲しい情報は予報(forecasts)の以下です。

dateLabel
date
telop

jqコマンドでの抽出
$ GET http://weather.livedoor.com/forecast/webservice/json/v1?city=140010 | jq -r '.forecasts | .[] | [ .dateLabel, .date, .telop]'
[
  "今日",
  "2020-06-11",
  "雨"
]
[
  "明日",
  "2020-06-12",
  "曇のち雨"
]
[
  "明後日",
  "2020-06-13",
  "曇時々雨"
]

整形

好みの形に整形します。

$ GET http://weather.livedoor.com/forecast/webservice/json/v1?city=140010 | jq -r '.forecasts | .[] | [ .dateLabel, .date, .telop] | @sh' | sed s/\'//g
今日 2020-06-11 雨
明日 2020-06-12 曇のち雨
明後日 2020-06-13 曇時々雨

スクリプト化(weather.sh)

#!/bin/bash
#-----------------------------------------
# 天気情報取得スクリプト
#-----------------------------------------

if [ $# -eq 0 -o _"$1" = _"-h" ]; then
  echo Usage : $(basename $0) 一次細分区ID
  echo 例 東京 : $(basename $0) 130010
  echo 例 横浜 : $(basename $0) 140010
  exit 1;
fi

city=$1
url=http://weather.livedoor.com/forecast/webservice/json/v1?city=${city}

#-----------------------------------------
# 一次細分区で指定した天気情報を得る。
#-----------------------------------------
GET ${url}                               |
#-----------------------------------------
# JSONから欲しい情報を得る。
#-----------------------------------------
jq -r '
    #-----------------------------
    # 予報部分の抽出
    #-----------------------------
    .forecasts                   |
    #-----------------------------
    # 配列の全要素を抽出(今日・明日・明後日)
    #-----------------------------
    .[]                          |
    #-----------------------------
    # 必要情報を抽出
    #-----------------------------
    [ .dateLabel, .date, .telop] |
    @sh'                                 |
#-----------------------------------------
# クォート(')を除外する。
#-----------------------------------------
sed s/\'//g

実行

$ ./weather.sh 140010
今日 2020-06-11 雨
明日 2020-06-12 曇のち雨
明後日 2020-06-13 曇時々雨

Prolog:3x3の魔法陣の解を求める

gprologのFDの練習として、SPINで以前に書いた3x3の魔法陣の解を求めてみます。gprologのソースファイルにはサンプルが含まれています。その中にmagsq.plがあり、魔法陣のサイズを指定できるものがあります。

ソース ms.pl

/* 魔法陣 */
ms(MS) :-

  MS = [ V11, V12, V13,
         V21, V22, V23,
         V31, V32, V33 ],

  /* 各要素は1〜9である。*/
  fd_domain(MS, 1, 9),

  /* 各要素の値は異なる。*/
  fd_all_different(MS),

  /* 行の和は15である。*/
  V11 + V12 + V13 #= 15,
  V21 + V22 + V23 #= 15,
  V31 + V32 + V33 #= 15,

  /* 列の和は15である。*/
  V11 + V21 + V31 #= 15,
  V12 + V22 + V32 #= 15,
  V13 + V23 + V33 #= 15,

  /* 対角の和は15である。*/
  V11 + V22 + V33 #= 15,
  V31 + V22 + V13 #= 15,

  fd_labeling(MS).

/* 出力用の述語 */
pp([V11, V12, V13, V21, V22, V23, V31, V32, V33]) :-
  write(V11), write(' '), write(V12), write(' '), write(V13), nl,
  write(V21), write(' '), write(V22), write(' '), write(V23), nl,
  write(V31), write(' '), write(V32), write(' '), write(V33), nl.

実行

$ gprolog
GNU Prolog 1.4.5 (64 bits)
Compiled Mar 17 2020, 23:55:22 with gcc
By Daniel Diaz
Copyright (C) 1999-2020 Daniel Diaz
| ?- ['ms.pl'].
:
| ?- ms(Ans),pp(Ans).
2 7 6
9 5 1
4 3 8

Ans = [2,7,6,9,5,1,4,3,8] ? ;
2 9 4
7 5 3
6 1 8

Ans = [2,9,4,7,5,3,6,1,8] ? ;
4 3 8
9 5 1
2 7 6

Ans = [4,3,8,9,5,1,2,7,6] ? ;
4 9 2
3 5 7
8 1 6

Ans = [4,9,2,3,5,7,8,1,6] ? ;
6 1 8
7 5 3
2 9 4

Ans = [6,1,8,7,5,3,2,9,4] ? ;
6 7 2
1 5 9
8 3 4

Ans = [6,7,2,1,5,9,8,3,4] ? ;
8 1 6
3 5 7
4 9 2

Ans = [8,1,6,3,5,7,4,9,2] ? ;
8 3 4
1 5 9
6 7 2

Ans = [8,3,4,1,5,9,6,7,2] ? ;

(1 ms) no

Prolog:1〜9の2つの整数で和が10になる組み合わせを求める

gprologのマニュアルを見ていると「9 Finite domain solver and built-in predicates」という章がありました。「9.1 introduction」にも「constraint solver」という記述があるので、何らかの制約問題を解いてくれるのかと思い試して見ました。

簡単そうなサンプルとして、以下のような問題を考えてみました。

  • A,B が1〜9の範囲の整数をとる。
  • A+Bが10となる組み合わせを考える。
  • A>=Bとする。

gplorgの起動

$ gprolog
GNU Prolog 1.4.5 (64 bits)
Compiled Mar 17 2020, 23:55:22 with gcc
By Daniel Diaz
Copyright (C) 1999-2020 Daniel Diaz
| ?- 

1〜9の範囲のA,Bを用意

| ?- fd_domain([A,B],1,9).

A = _#0(1..9)
B = _#17(1..9)

yes

それぞれに値を代入してみる

| ?- fd_domain([A,B],1,9), fd_labeling([A,B]).

A = 1
B = 1 ? ;

A = 1
B = 2 ? ;

A = 1
B = 3 ? 

yes

組み合わせがどんどん出てきます。上記の例では止めていますが、A=9,B=9の組み合わせまで出てきます。

A+Bが10となる制約

| ?- fd_domain([A,B],1,9), A + B #= 10, fd_labeling([A,B]).

A = 1
B = 9 ? a

A = 2
B = 8

A = 3
B = 7

A = 4
B = 6

A = 5
B = 5

A = 6
B = 4

A = 7
B = 3

A = 8
B = 2

A = 9
B = 1

(1 ms) yes

A + B = 10 となる組み合わせが出てきますが、3+7=10, 7+3=10 のような組み合わせを省くため、大小関係の制約を追加します。

大小関係の制約 A >= Bの追加

| ?- fd_domain([A,B],1,9), A + B #= 10, A #>= B, fd_labeling([A,B]).

A = 5
B = 5 ? ;

A = 6
B = 4 ? ;

A = 7
B = 3 ? ;

A = 8
B = 2 ? ;

A = 9
B = 1

(1 ms) yes

Prolog:アローダイアグラム クリティカルパスを求める問題(基本情報処理技術者試験)を解いてみる

基本情報技術者試験で出てくるアローダイヤグラムクリティカルパスを求める問題ですが、prologを使うとスッキリかけるのではないかと思い挑戦してみました。使用したprologの処理系はgprologとなります。

問題は以下のようなものでした。

f:id:hihdrum:20200330231536p:plain
問題のダイアグラム

問題の情報をprologで表現したものが以下となります。

ブランチ情報

% ブランチ情報
% branchInfo(ブランチ名, 元ノード, 先ノード, コスト).
branchInfo(a,   n1, n2, 1).
branchInfo(b,   n1, n3, 5).
branchInfo(c,   n2, n3, 3).
branchInfo(d,   n2, n4, 3).
branchInfo(e,   n3, n5, 4).
branchInfo(dmy, n5, n4, 0).
branchInfo(f,   n5, n6, 5).
branchInfo(g,   n4, n6, 4).

ブランチ情報からパス情報を得る述語を次のように定義しました。

パス

% パス
%path(元ノード, 先ノード, [ブランチ情報]).
path(StartNode, EndNode, [branchInfo(PathName, StartNode, EndNode, Weight)]) :-
  branchInfo(PathName, StartNode, EndNode, Weight).

path(StartNode, EndNode, [branchInfo(PathName, StartNode, NextStart, Weight)|T]) :-
  branchInfo(PathName, StartNode, NextStart, Weight),
  path(NextStart, EndNode, T).

これでStartNodeからEndNodeに至るブランチのリストが得られます。
例としてn1からn3のパスを確認してみます。

パスの確認(n1->n3)

| ?- path(n1,n3,P).

P = [branchInfo(b,n1,n3,5)] ? ;

P = [branchInfo(a,n1,n2,1),branchInfo(c,n2,n3,3)] ? ;

no

続いてブランチの名前とコストを集める述語を定義しました。

% pathCost(元ノード, 先ノード, result(パス, コスト)).
pathCost(StartNode, EndNode, result(Rout, Cost)) :-

  % パスを得る。
  path(StartNode, EndNode, R),

  % パスの経路名を集める。
  maplist(branchName, R, Rout),

  % パスのコストを得る。
  maplist(weight, R, Ws), sum_list(Ws, Cost).

branchName(branchInfo(Name,_,_,_), Name).
weight(branchInfo(_, _, _, Weight), Weight).

prologならではなのですが、pathCost(n1,n6,R)で問い合わせて「;」や「a」でn1からn6に至る各ルートのコストが得られるのでほぼ、答えは得ているような感じになりました。

パスのコスト(n1->n6)

| ?- pathCost(n1,n6,R).

R = result([a,c,e,f],13) ? ;

R = result([a,c,e,dmy,g],12) ? a

R = result([a,d,g],8)

R = result([b,e,f],14)

R = result([b,e,dmy,g],13)

no

クリティカルパスを求める述語を定義します。

クリティカルパス

% クリティカルパスを得る。
criticalPath(StartNode, EndNode, CriticalPath) :-

  % 各ルートとコスト情報を得る。
  findall(D,pathCost(StartNode,EndNode,D),Ds),

  % 最大コストの場所を得る。
  maplist(cost,Ds,Cs), max_list(Cs,Max), nth(Pos,Cs,Max),

  % クリティカルパスの情報を得る。
  nth(Pos,Ds,CriticalPath).

cost(result(_,C), C).

クリティカルパス(n1->n6)

クリティカルパスを問い合わせてみます。

| ?- criticalPath(n1,n6,Cp).

Cp = result([b,e,f],14) ? ;

no

一応全体は以下のようになります。

コード全体

% ブランチ情報
% branchInfo(ブランチ名, 元ノード, 先ノード, コスト).
branchInfo(a,   n1, n2, 1).
branchInfo(b,   n1, n3, 5).
branchInfo(c,   n2, n3, 3).
branchInfo(d,   n2, n4, 3).
branchInfo(e,   n3, n5, 4).
branchInfo(dmy, n5, n4, 0).
branchInfo(f,   n5, n6, 5).
branchInfo(g,   n4, n6, 4).

% パス
%path(元ノード, 先ノード, [パス情報]).
path(StartNode, EndNode, [branchInfo(PathName, StartNode, EndNode, Weight)]) :-
  branchInfo(PathName, StartNode, EndNode, Weight).

path(StartNode, EndNode, [branchInfo(PathName, StartNode, NextStart, Weight)|T]) :-
  branchInfo(PathName, StartNode, NextStart, Weight),
  path(NextStart, EndNode, T).

% pathCost(元ノード, 先ノード, result([ブランチ名], コスト)).
pathCost(StartNode, EndNode, result(BranchNames, Cost)) :-

  % ルートを得る。
  path(StartNode, EndNode, R),

  % ルートの経路名を集める。
  maplist(branchName, R, BranchNames),

  % ルートのコストを得る。
  maplist(weight, R, Ws), sum_list(Ws, Cost).

branchName(branchInfo(Name,_,_,_), Name).
weight(branchInfo(_, _, _, Weight), Weight).

% クリティカルパスを得る。
criticalPath(StartNode, EndNode, CriticalPath) :-

  % 各ルートとコスト情報を得る。
  findall(D,pathCost(StartNode,EndNode,D),Ds),

  % 最大コストの場所を得る。
  maplist(cost,Ds,Cs), max_list(Cs,Max), nth(Pos,Cs,Max),

  % クリティカルパスの情報を得る。
  nth(Pos,Ds,CriticalPath).

cost(result(_,C), C).

SPIN:1信号(赤青黃)の遷移サンプル

「SPINによる設計モデル検証」のNever Claimの2信号サンプルがあるのですが、自身の勉強としてもっと簡単なサンプルから作成してみました。モデルとしては、赤から青になり、青から黄、黄から赤になる信号のモデルと、Never Claimを記述します。

記述したファイルは以下となります。

signal3.pml

/* 信号の色 */
mtype = { BLUE, YELLOW, RED }
mtype light = RED;

active proctype signal()
{
  do
  :: atomic {
       light == RED ->
         if
         :: light = BLUE;
         :: light = RED;
         fi
     }
  :: atomic {
       light == BLUE ->
         if
         :: light = YELLOW;
         :: light = BLUE;
         /* バグを仕込むと検出される。*/
         //:: light = RED;
         fi
     }
  :: atomic {
       light == YELLOW ->
         if
         :: light = RED
         :: light = YELLOW;
         fi
     }
  od
}

never n01
{
  /* 信号:赤からの遷移 */
  LIGHT_RED:
    if
    :: light == RED -> goto LIGHT_RED
    :: light == BLUE -> goto LIGHT_BLUE
    :: else -> goto accept
    fi

  /* 信号:青からの遷移 */
  LIGHT_BLUE:
    if
    :: light == BLUE -> goto LIGHT_BLUE
    :: light == YELLOW -> goto LIGHT_YELLOW
    :: else -> goto accept
    fi

  /* 信号:黃からの遷移 */
  LIGHT_YELLOW:
    if
    :: light == YELLOW -> goto LIGHT_YELLOW
    :: light == RED -> goto LIGHT_RED
    :: else -> goto accept
    fi
 
  accept:
    skip;
    goto accept
}

検証器の作成と実行

$ spin -a -run -a signal3.pml 
warning: for p.o. reduction to be valid the never claim must be stutter-invariant
(never claims generated from LTL formulae are stutter-invariant)

(Spin Version 6.5.0 -- 17 July 2019)
	+ Partial Order Reduction

Full statespace search for:
	never claim         	+ (n01)
	assertion violations	+ (if within scope of claim)
	acceptance   cycles 	+ (fairness disabled)
	invalid end states	- (disabled by never claim)

State-vector 20 byte, depth reached 11, errors: 0
        6 states, stored
        7 states, matched
       13 transitions (= stored+matched)
        6 atomic steps
hash conflicts:         0 (resolved)

Stats on memory usage (in Megabytes):
    0.000	equivalent memory usage for states (stored*(State-vector + overhead))
    0.287	actual memory usage for states
  128.000	memory used for hash table (-w24)
    0.534	memory used for DFS stack (-m10000)
  128.730	total actual memory usage


unreached in proctype signal
	signal3.pml:32, state 22, "-end-"
	(1 of 22 states)
unreached in claim n01
	signal3.pml:63, state 27, "-end-"
	(1 of 27 states)

pan: elapsed time 0 seconds

バグを仕込む

signal3.pmlのコメントアウトしている「//:: light = RED;」をコメント解除すると、エラーが検出されてきます。

$ spin -a -run -a signal3.pml 
warning: for p.o. reduction to be valid the never claim must be stutter-invariant
(never claims generated from LTL formulae are stutter-invariant)
pan:1: acceptance cycle (at depth 12)
pan: wrote signal3.pml.trail

(Spin Version 6.5.0 -- 17 July 2019)
Warning: Search not completed
	+ Partial Order Reduction

Full statespace search for:
	never claim         	+ (n01)
	assertion violations	+ (if within scope of claim)
	acceptance   cycles 	+ (fairness disabled)
	invalid end states	- (disabled by never claim)

State-vector 20 byte, depth reached 20, errors: 1
       10 states, stored
        6 states, matched
       16 transitions (= stored+matched)
       10 atomic steps
hash conflicts:         0 (resolved)

Stats on memory usage (in Megabytes):
    0.000	equivalent memory usage for states (stored*(State-vector + overhead))
    0.286	actual memory usage for states
  128.000	memory used for hash table (-w24)
    0.534	memory used for DFS stack (-m10000)
  128.730	total actual memory usage



pan: elapsed time 0 seconds

trailの確認

信号が赤(初期値)から青、赤と遷移した際にNever claimのaccetpにジャンプすることになり、最終的にエラーとして検出されています。

$ spin -t -p -g signal3.pml
starting claim 1
Never claim moves to line 39	[((light==RED))]
  2:	proc  0 (signal:1) signal3.pml:9 (state 1)	[((light==RED))]
  3:	proc  0 (signal:1) signal3.pml:11 (state 2)	[light = BLUE]
		light = BLUE
Never claim moves to line 40	[((light==BLUE))]
  5:	proc  0 (signal:1) signal3.pml:16 (state 7)	[((light==BLUE))]
  6:	proc  0 (signal:1) signal3.pml:19 (state 9)	[light = BLUE]
Never claim moves to line 47	[((light==BLUE))]
  8:	proc  0 (signal:1) signal3.pml:16 (state 7)	[((light==BLUE))]
  9:	proc  0 (signal:1) signal3.pml:21 (state 10)	[light = RED]
		light = RED
Never claim moves to line 49	[else]
 11:	proc  0 (signal:1) signal3.pml:9 (state 1)	[((light==RED))]
 12:	proc  0 (signal:1) signal3.pml:11 (state 2)	[light = BLUE]
		light = BLUE
  <<<<<START OF CYCLE>>>>>
Never claim moves to line 61	[(1)]
 14:	proc  0 (signal:1) signal3.pml:16 (state 7)	[((light==BLUE))]
 15:	proc  0 (signal:1) signal3.pml:18 (state 8)	[light = YELLOW]
		light = YELLOW
 17:	proc  0 (signal:1) signal3.pml:25 (state 14)	[((light==YELLOW))]
 18:	proc  0 (signal:1) signal3.pml:27 (state 15)	[light = RED]
		light = RED
 20:	proc  0 (signal:1) signal3.pml:9 (state 1)	[((light==RED))]
 21:	proc  0 (signal:1) signal3.pml:11 (state 2)	[light = BLUE]
		light = BLUE
spin: trail ends after 21 steps
#processes: 1
		light = BLUE
 21:	proc  0 (signal:1) signal3.pml:7 (state 20)
 21:	proc  - (n01:1) signal3.pml:61 (state 25)
1 processes created

GCC:関数重複定義(Multiple Definition)を-z muldefsで許可する

同僚の方が一時的な確認のために同名の関数を静的リンクした実行ファイルを作成しようとしてエラーになっていました。C言語ではできないんじゃないかな。と思っていたのですが、「HPではできたのに〜」と言っていたのを聞き、ならばGCCでもオプションでどうにかなるのではと思って調べてみました。

結果、リンクオプション -z muldefs を利用することで可能でした。
使用したファイルは以下となります。

func.h

簡易的にするためインクルードガードは省いています。

int func(int x);

func01.c

#include "func.h"

int func(int x)
{
  return x + 10;
}

func02.c

#include "func.h"

int func(int x)
{
  return x + 100;
}

main.c

#include <stdio.h>
#include "func.h"

int main(void)
{
  int x = func(10);

  printf("func(10) = %d\n", x);

  return 0;
}

通常のコンパイル

普通にコンパイルしてみます。

$ gcc -Wall main.c func01.c func02.c 
/tmp/ccKwhV9A.o: 関数 `func' 内:
func02.c:(.text+0x0): `func' が重複して定義されています
/tmp/ccGuOvnL.o:func01.c:(.text+0x0): ここで最初に定義されています
collect2: error: ld returned 1 exit status

funcが重複してリンクが失敗しています。

マニュアルにあたる

ldのマニュアルを見ると -zオプションのキーワードを指定すると重複定義を許可してくれそうです。(ld --helpでも見つけられました。)

$ man ld
:
       -z keyword
           The recognized keywords are:
:
           muldefs
               Allow multiple definitions.

リンクオプション -z muldefsを試してみる

リンク時のエラーが解消されています。また、今回試した例では、左側の定義が有効になっているようです。

$ gcc -Wall -z muldefs main.c func01.c func02.c 
$ ./a.out 
func(10) = 20
$ gcc -Wall -z muldefs main.c func02.c func01.c 
$ ./a.out 
func(10) = 110