2017年5月26日 星期五

linux samba 相關操作

最近 (20170513) WannaCry 勒索病毒搞的人心惶惶, smb protocol 似乎有什麼問題被拿來利用了, 不過還是紀錄一下 smb 相關用法。

mount samba filesystem in linux

# mkdir /mnt/cifs
# mount -t cifs //server-name/share-name /mnt/cifs -o username=shareuser,password=sharepassword,domain=nixcraft
# mount -t cifs //192.168.101.100/sales /mnt/cifs -o username=shareuser,password=sharepassword,domain=nixcraft
mount -t cifs //192.168.121.32/descent /mnt/cifs/  -o username=myname,password=mypwd

ref: http://www.cyberciti.biz/faq/linux-mount-cifs-windows-share/

samba access symbolic link
ref: http://www.ubuntu-tw.org/modules/newbb/viewtopic.php?post_id=128058
edit smb.conf

follow symlinks = yes
unix extensions = no
wide links = yes


使用 smbclient copy smb server 檔案


ref: http://www.linuxso.com/command/smbclient.html
--user username%passwd, 此例子的密碼是空字串 ''

get <remote file name>[local file name]
Copy the file called remote file name from the server to the
machine running the client. If specified, name the local copy local
file name. Note that all transfers in smbclient are binary. See
also the lowercase command.

from samba server copy to local
$ smbclient -c "get e.iso /tmp/e.iso" \\\\127.0.0.1\\smb_test --user username%''

put<local file name> [remote file name]
Copy the file called local file name from the machine running the

client to the server. If specified, name the remote copy remote
file name. Note that all transfers in smbclient are binary. See
also the lowercase command.

from local copy to samba server copy
$ /usr/bin/smbclient -c "put /tmp/exam.d e.txt" //127.0.0.1/smb_test/ --user username%''

2017年5月19日 星期五

程式真的要寫成這麼聰明嗎?

Alan Perlis, Alan Perlis QuotesA programming language is low level when its programs require attention to the irrelevant.

代码执行的效率》裡頭提到一個效率問題,

代码执行的效率》有效率的改法
if (data[j] >= 128)
sum += data[j];
变成:
int t = (data[j] - 128) >> 31;
sum += ~t & data[j];

這個看似聰明的改法帶來了什麼樣難以察覺的陷阱呢?

不想看答案的可以先想想看。

b.cpp
 1 #include <stdio.h>
 2 #include <stdint.h>
 3 #include <iostream>
 4 
 5 using namespace std;
 6 
 7 int main(int argc, char *argv[])
 8 {
 9   int data[10] = {1,2,3,4,200,6,7,8,300,10};
10   int sum=0;
11 
12   for (int j=0 ; j < 10 ; ++j)
13   {
14     int t = (data[j] - 128) >> 31;
15     //printf("     %u ## t: %u, ~t: %u, ~t & data[j]: %u\n", j, t, ~t, ~t & data[j]);
16     cout << "cout " << j << " ## t: " << t << ", ~t: " << ~t << ", ~t & data[j]: " << (~t & data[j]) << endl;
17     sum += ~t & data[j];
18     #if 0
19     if (data[j] >= 128)
20       sum += data[j];
21     #endif
22   }
23   //printf("sum: %u\n", sum); 
24   cout << "sum: " << sum << endl;
25   return 0;
26 }

b.cpp 執行結果
cout 0 ## t: -1, ~t: 0, ~t & data[j]: 0
cout 1 ## t: -1, ~t: 0, ~t & data[j]: 0
cout 2 ## t: -1, ~t: 0, ~t & data[j]: 0
cout 3 ## t: -1, ~t: 0, ~t & data[j]: 0
cout 4 ## t: 0, ~t: -1, ~t & data[j]: 200
cout 5 ## t: -1, ~t: 0, ~t & data[j]: 0
cout 6 ## t: -1, ~t: 0, ~t & data[j]: 0
cout 7 ## t: -1, ~t: 0, ~t & data[j]: 0
cout 8 ## t: 0, ~t: -1, ~t & data[j]: 300
cout 9 ## t: -1, ~t: 0, ~t & data[j]: 0
sum: 500

哇! 真的可以計算正確結果耶! 估計你開始佩服想出這個寫法的人了。再來看看另外的例子 a.cpp。

a.cpp
 1 #include <stdio.h>
 2 #include <stdint.h>
 3 #include <iostream>
 4 
 5 using namespace std;
 6 
 7 int main(int argc, char *argv[])
 8 {
 9   uint32_t data[10] = {1,2,3,4,200,6,7,8,300,10};
10   uint32_t sum=0;
11 
12   for (int j=0 ; j < 10 ; ++j)
13   {
14     uint32_t t = (data[j] - 128) >> 31;
15     //printf("     %u ## t: %u, ~t: %u, ~t & data[j]: %u\n", j, t, ~t, ~t & data[j]);
16     cout << "cout " << j << " ## t: " << t << ", ~t: " << ~t << ", ~t & data[j]: " << (~t & data[j]) << endl;
17     sum += ~t & data[j];
18     #if 0
19     if (data[j] >= 128)
20       sum += data[j];
21     #endif
22   }
23   //printf("sum: %u\n", sum); 
24   cout << "sum: " << sum << endl;
25   return 0;
26 }

a.cpp 執行結果<
cout 0 ## t: 1, ~t: 4294967294, ~t & data[j]: 0
cout 1 ## t: 1, ~t: 4294967294, ~t & data[j]: 2
cout 2 ## t: 1, ~t: 4294967294, ~t & data[j]: 2
cout 3 ## t: 1, ~t: 4294967294, ~t & data[j]: 4
cout 4 ## t: 0, ~t: 4294967295, ~t & data[j]: 200
cout 5 ## t: 1, ~t: 4294967294, ~t & data[j]: 6
cout 6 ## t: 1, ~t: 4294967294, ~t & data[j]: 6
cout 7 ## t: 1, ~t: 4294967294, ~t & data[j]: 8
cout 8 ## t: 0, ~t: 4294967295, ~t & data[j]: 300
cout 9 ## t: 1, ~t: 4294967294, ~t & data[j]: 10
sum: 538

發現結果不正確了, 僅僅是 type 的不同, 程式就不正確了。你願意用這個演算法嗎? 這個演算法在某些時候是對的, 某些時候是錯的, 你能掌握在什麼時機才是正確的嗎?

第一點: 這寫法很難懂
第二點: 不一定總是正確

如果你能掌握型別的話, 那還有一個規則是你不能掌握的。最後在提醒一個 c 語言規則, 有號數的右移運算, 是 implementation-defined (ref: 1.2. 移位运算)。

當然查閱 c 規格書最具有權威性, 但那個不好看, 如果要在精確一點的中文資料, 可以參考《标准C语言指南》6.18 (p341), 將移位運算式做了詳細的說明, 看完之後應該會覺得, 嗯 ... c 真難。

相信這麼一來, 之前的佩服感應該是完全消失了, c 沒有這麼簡單, 她是程式語言, 不是數學。

我不熟習 java, 有人可以說說 java 的行為嗎?

既然談到 Bitshift Operators 順便說說之前遇到的一個問題。

t.c
 1 #include <stdio.h>
 2 #include <stdint.h>
 3 int main(int argc, char *argv[])
 4 {
 5   uint64_t temp;
 6
 7   temp = (1 << 32);
 8   printf("temp: %lu\n", temp);
 9   return 0;
10 }

t.c 看似理所應當的程式碼, c 編譯器發出了 warning:

warning
1 t.c: In function 'main':
2 t.c:7:13: warning: left shift count >= width of type [-Wshift-count-overflow]
3    temp = (1 << 32);

怎麼回事? 1 的 type 是 int, 不是 uint_64_t, 也不是其他 type, 就是 int, 在我的平台, 是 32bit, 而 shift 32 bit 超出了 32bit 的範圍, 這是 Undefined (ref: 1.2. 移位运算), 這回我參考了 c 標準規格書 (ref: figure 1, 第 3 點), 而不是如人所想因為等號左邊有個 uint64_t temp 就會將結果轉型成 uint64_t, 這是 c 語言很難相處之道。

figure 1. N1570 6.5.7

解法: 1ul << 32, 明白告訴 c 編譯器, 我的 1 是 unsigned long (在我的環境是 64bit)

2017年5月12日 星期五

作業系統之前的程式 for qemu arm/vexpress-a9 (0) - hello world

天地無極, 乾坤借法
我忽然興起在 qemu arm 中寫一個 bare metal hello world 的念頭, 這不是什麼新鮮事, 《一步步写嵌入式操作系统:ARM编程的方法与实践》裡頭用的就是這個方法, 只是作者用的模擬器不是 qemu, 而是 skyeye, 但我想用 qemu, 找到了《Hello world for bare metal ARM using QEMU 》, 不過這篇文章介紹的是 VersatilePB platform, 是 ARM926EJ-S core, 你一定和我有同樣的想法, 太舊了, 我想找 coretex a v7 系列的版本, 不過先來懷舊一下。

我已經有了樹莓派 2, 為什麼還需要用模擬器呢?

為了除錯, 我沒有可以在了樹莓派上用的 ice/jtag, 對於開發 bare metal 程式實在太困難, 總不能老是靠「冥想」, 有了 qemu arm 搭配 gdb 的除錯功能, 會有事半功倍的效果。

source code:
https://github.com/descent/bare-metal_qemu_arm_v7a

VersatilePB version
startup.s test.c test.ld

節錄 Hello world for bare metal ARM using QEMU
he QEMU emulator is written especially to emulate Linux guest systems; for this reason its startup procedure is implemented specifically: the -kernel option loads a binary file (usually a Linux kernel) inside the system memory starting at address 0x00010000. The emulator starts the execution at address 0x00000000, where few instructions (already in place) are used to jump at the beginning of the kernel image. The interrupt table of ARM cores, usually placed at address 0x00000000, is not present, and the peripheral interrupts are disabled at startup, as needed to boot a Linux kernel. Knowing this, to implement a working emulation I need to considerate a few things:

  • The software must be compiled and linked to be placed at 0x00010000
  • I need to create a binary image of our program
  • I can ignore interrupt handling for now

qemu load -kernel 後的檔案到 0x00010000, 但卻是從 0x00000000 執行, 有點不太理解。

而 qemu/arm 模擬的開發板 source code 在:

qemu/hw/arm
vexpress.c
versatilepb.c

可以看到其對 uart register 的定義。

hello world 程式只有 2 個檔案, 很容易理解, 最主要是設定 stack, call c function, 寫 uart register 完成字元的輸出。

make ARM926EJ_S=1
arm-none-eabi-as -g startup.s -o startup.o
arm-none-eabi-gcc -DARM926EJ_S -mcpu=arm926ej-s -c -g test.c -o test.o
arm-none-eabi-ld -g -T test.ld test.o startup.o -o test.elf
arm-none-eabi-objcopy -O binary test.elf test.bin

list 1
1 descent@debian64:bare-metal_qemu_arm_v7a$ qemu-system-arm -M versatilepb -m 128M -nographic -kernel test.bin
2 pulseaudio: set_sink_input_volume() failed
3 pulseaudio: Reason: Invalid argument
4 pulseaudio: set_sink_input_mute() failed
5 pulseaudio: Reason: Invalid argument
6 Hello world!
7 hello ARM926EJ-S

ctrl+a x 可以離開 qemu。

list 1. L6 秀出了 hello ARM926EJ-S, 有沒很感動。

再來是 VEXPRESS-A9 的版本, 我以為如法泡製, 將 uart base address 改成 0x10009000 即可, 事實上, 我吃了更多的苦頭才找到答案。

VEXPRESS-A9 version
startup_vexpress_a9.o test.c vexpress_a9.ld

首先 qemu 指令要改成 -bios
qemu-system-arm -M vexpress-a9 -m 128M -bios test.a9.bin -nographic

好不容易看到 qemu 有畫面了, 但是是錯誤的訊息, 我一開始以為是 stack 沒設好的關係 (事實上也是啦), 透過反組譯後, 發現原本的 0x10000 記憶體 (我拿這段位址當 stack 區域) 無法寫入, 在參考 http://infocenter.arm.com/help/topic/com.arm.doc.dui0448i/DUI0448I_v2p_ca9_trm.pdf p3-3, 0x6000_0000-0x7FFF_FFFF 512MB Local DDR2 lower, use the ddr2 address.

使用 0x70000000 這個位址來設定 stack 就對了, 然後把 qemu 記憶體大小改成 512M。

qemu-system-arm -M vexpress-a9 -m 512M -bios test.a9.bin -nographic

list 1
1 descent@debian64:bare-metal_qemu_arm_v7a$ qemu-system-arm -M vexpress-a9 -m 512M  -bios test.a9.bin --nographic
2 pulseaudio: set_sink_input_volume() failed
3 pulseaudio: Reason: Invalid argument
4 pulseaudio: set_sink_input_mute() failed
5 pulseaudio: Reason: Invalid argument
6 Hello world!
7 hello VEXPRESS_A9

終於一舉功成。

另外 linker script 起始位置改為 0x0, 這樣才可以配合 gdb 來 single step。
在 gdb 下

x/8xb 0

可以看到載入的 test.a9.bin (linker script 起始位置為 0x10000 也一樣在 0x0 這裡看得到, 其實把 linker script 起始位置改為 0x0 也可以的), 就在這裡。

以下連結的程式碼也可以參考, 有 gic 的設定。該作者用 svn, 可用以下 svn 指令抓取 soure code。
http://ellcc.org/blog/?p=10
Updating From Outside Sources
svn co http://ellcc.org/svn/ellcc/trunk/baremetal
svn ls http://ellcc.org/svn/ellcc/trunk/

能成功設定 gic + timer 才是我的終極目的。若你比我先完成了, 請通知我讓我抄一下, 這樣我就不用那麼累了。

最後還是要提醒一下, 模擬器畢竟是模擬器, 和真實機器還是有點差異, 例如 uart 的設定不會這麼簡單, 通常還有 Baud 要設定。為了不讓自己白努力一場, 請在真實機器驗證整個模擬環境的程式碼, 正常來說, 應該還要修修改改。

最後的最後再提醒第二下, 這個程式碼看起來好像能使用 c 語言了, 好像可以正常呼叫 c function, 但其實離真正可用的 c 環境還有點距離, 若隨意使用 c 的各種宣告 (例如沒有初始值的全域變數), 是有可能出問題的。

因為 bss 沒設定, data section 也沒處理好, 很容易就踩雷了。我另外寫了一份完整版的 c runtime, 請參考 source code makefile 得知 v_a9.elf 相關的程式檔案, 這個版本打造了完整的 c runtime, bss/data section 均做了相關的初始化 。

本來我打算整合到 simple c++ library 當中的, 但我的目的僅僅是 gic+timer, 就不分心在其他事情上了。

若要練習的朋友, 請從 v_a9.elf 開始。

目前的版本無法在 vexpress-a15 qemu 上執行, 若你成功改到 vexpress-a15 qemu 上執行, 可以通知我一下嗎? 這可是幫了我很大的忙。

ref:

2017年5月5日 星期五

DD 可動手/支架/牧瀨-紅莉栖

订单号2899928293269095动漫宅的福音之路
385.00
1
申请售后
投诉卖家
827.00
(含运费:¥12.00)
交易成功
430.00
1
申请售后
投诉卖家


827rmb = 3869 nt 含 38 手續費 (玉山支付)。

支架 20170215 發貨, 20170216 到集貨商 1.1kg
可動手 20170218 (maybe 0219) 發貨, 20170220 到集貨商 0.27 kg


20161225 拍下。

20170223 發台灣。
20170228 收到。

從拍下到拿到, 經過了2個月, 等了非常久, 但東西還不錯。目前價格已經調漲了。原本是打算希望能在 2017 農曆過年前拿到, 結果到了 20170228 才收到。

可動手雖然是訂購普肌, 但是和 DD 的普肌比起來白了點。照片可能看不出來, 但實際上就是白了一點。而且不知道為什麼多了一個類似手環的結構, 安裝到 DD 手臂時, 有點不好安裝。

賣家說可以寄回更換新版, 解決了色差問題, 但我到寫完這篇 (20170505), 還遲遲沒決定要不要寄回更換。

不知道為什麼 volks 不研發可動手, 以日本的技術應該不會做不到, 但不知道為什麼不做, 反倒是出了好幾款手型, 我另外購入了 "呀比" 的大手, 但由於我不會換, 所以從來沒安裝過。

賣家似乎很忙, 透過旺信詢問的訊息有時候很久才會回, 但賣家很客氣, 也對於拖了這麼久給了我另外的零件當作補償。

剛收到打開時, 有很強烈的臭味, 建議放到通風處等味道散去。仔細看沒有 DD 固定手勢的細緻, 感覺有點粗大, 但看起來也還不錯, 可以隨意使用各種手勢真的很過癮。我馬上就測試了 "呀比" 手勢, 看起來還真的像一回事, 買的 "呀比" 大手似乎派不上用場了。volks 販賣的手勢似乎都能作到, 愛心手就比較難一點, 但我覺得愛心手並不是太實用的手勢, 不會時時都用愛心手勢。



不錯吧, "呀比" 手勢以及拿著書包的手勢看起來沒什麼問題。可動手材質是矽膠, 看起來不是好保養的材質, 需要用痱子粉或是紙膠帶清除附著的灰塵。

另外同賣家的產品是娃娃支架, 這是一個有點複雜的組合式支架, 得花點功夫才組裝的起來, 使用上也有點複雜, 但是我喜歡它的特殊效果。


不只可以懸空橫向, 調整得好的話, 也可以整個倒立, 不過我覺得底盤不是很穩, 要調整好要花點時間, 我大概花了一個小時在玩這個支架。



可動手、支架談完了, 該來說說牧瀨紅莉栖, 我第一次看到是 azone 的版本, 覺得很漂亮, 入手 DD 後很希望收一套牧瀨紅莉栖的衣服, 沒想到還真的有人做。

298.00
292.04
1
投诉卖家
医师外套
98.00
96.04
1
投诉卖家
靴子
98.00
96.04
1
投诉卖家

20170206 拍下。
20170223 發台灣。
20170228 收到。

沒有猶豫, 我當下就決定購入, 只是等到 20170206 才拍下, 大概過了 3 星期才收到。這價錢可以買一隻 figure 了, DD 坑真的太大了。衣服製作得很精美, 很滿意, 可惜還是和 azone 整體感覺有所不同, 有些小小的不同, 剛好有件皮裙, 原本的皮褲改成皮裙試試, 也還蠻搭的。



醫生大衣還真的很大件, 咖啡薄外套也很不錯, 靴子也很漂亮, 不過褲襪是黑色的, 不是咖啡色, 不是什麼問題, 自己另外買就可以, 黑色褲襪不是很緊, 有點鬆鬆的, 穿著效果不是很好, 比不上我另外購買的那些。

牧瀨-紅莉栖 (まきせ くりす) 原來是遊戲《命運石之門》的角色, 我原本還以為是動畫人物, steam 上有這個遊戲, 已經列入 wish list 中。



要打造一隻喜愛角色的人物, 並沒有想像中的容易, 衣服造型只是最基本的要求, 有時候整個裝扮就是和想像中的有所不同, 我覺得 azone 的造型還是比較好看, 我試著參考 azone 官網照片的牧瀨-紅莉栖, 模仿其造型, 但整體感覺就是沒有azone 的好看。


2017年4月28日 星期五

java 編譯/執行環境 (2) - 建立 java jar 執行檔

執行一個 java 程式看來有點麻煩, 要知道所有的 class path, 哪些 class 在哪裡, 很麻煩, 有沒有類似像 c/c++ 這樣編譯出一個執行檔就可以呢?

jar 出現了, 它其中之一的功能就是打包需要的 class, 製作出一個獨立的執行檔。

以下都在這個目錄操作這些指令。
descent@debian64:java$ pwd
/home/descent/git/progs/java

編寫 manifest.txt
1 Main-Class: DescentPackage.HelloWorld

manifest.txt 有很多參數, jar 執行檔需要 Main-Class 這個參數, 填上它吧。

當然要先把 *.class 透過 javac 編譯出來。
descent@debian64:java$ ls DescentPackage/*.class
DescentPackage/HelloWorld.class  DescentPackage/PrintString.class

# 產生 jar 執行檔
jar -cvmf manifest.txt h.jar  DescentPackage/*.class

# 執行這個 jar 執行檔
descent@debian64:java$ java -jar ./h.jar 
yy Hello World
XX Hello World

在 linux 下直接執行這個 jar 執行檔。

apt-get install binfmt-support
escent@debian64:java$ chmod 755 h.jar 
descent@debian64:java$ ./h.jar 
yy Hello World
XX Hello World

在 windows 執行這個 jar 也是沒問題的 (fig 1)。

fig 1. 在 windows 7 執行在 linux 編譯的 jar 執行檔

如果你不信邪, 想試試在 DescentPackage 目錄產生 jar 執行檔, 會發現可以建立 hh.jar, 卻怎麼都無法執行。
descent@debian64:DescentPackage$ pwd
/media/work/git/progs/java/DescentPackage
descent@debian64:DescentPackage$ jar -cvmf manifest.txt hh.jar  *.class 
added manifest
adding: HelloWorld.class(in = 516) (out= 341)(deflated 33%)
adding: PrintString.class(in = 419) (out= 292)(deflated 30%)

descent@debian64:DescentPackage$ java -jar ./hh.jar 
Error: Could not find or load main class DescentPackage.HelloWorld
descent@debian64:DescentPackage$ java -cp .. -jar ./hh.jar 
Error: Could not find or load main class DescentPackage.HelloWorld

先這樣, 我不想在搞這個了, 有夠煩。

ref:
How to run a jar file in a linux commandline

2017年4月21日 星期五

java 編譯/執行環境 (1) - 引用別的檔案的 class

再來要說明怎麼把程式碼分開到不同的檔案, 並引用不同檔案的 class, 編譯與執行的動作開怎麼進行。

由於 java 奇怪的路徑尋訪, 我必須列出我目前在那個目錄, 避免我自己搞不清楚到底為什麼又找不到 class 了。

我在那個目錄
/home/descent/git/progs/java/DescentPackage
descent@debian64:DescentPackage$ ls *.java
HelloWorld.java  PrintString.java

按照 java 規定, 只能有一個 main method, 所以 HelloWorld.java 相當於 c++ main 的程式進入點, java 程式就是從這裡開始執行。

由於使用了 DescentPackage 這個 namespace, 所以這兩個檔案都得建立在 DescentPackage (按, 好蠢, java 還建議用 com.google.www 這種 namespace, 辛苦你了, 得建立不少層目錄, 你一定靠 IDE 搞定這事吧, 但手工打造一次, 你才能體會這種愚蠢) 這個目錄裡頭。

HelloWorld.java L8 使用了 PrintString 這個 class, 這個 class 在別的檔案裡頭, 所以用上了 L2 的 import, 所以實際上這個 class 全名是 DescentPackage.PrintString。不用 import 的話, 得改成

DescentPackage.PrintString ps = new DescentPackage.PrintString();

很長, 我知道。

HelloWorld.java
 1 package DescentPackage;
 2 import DescentPackage.*;
 3 
 4 public class HelloWorld
 5 {
 6   public static void main(String[] args) 
 7   {
 8     PrintString ps = new PrintString();
 9     System.out.println("Hello World");
10     ps.msg();
11   }
12 }


那這個 PrintString.java 要怎麼寫呢? class name 要和檔名一樣 (還是覺得很蠢的設計), 我很討厭大小寫混在一起的檔名, 因為在 linux 這種有區分大寫的檔名的檔案系統, 這很麻煩 (c/c++ 的 header files), 我通常會用全小寫檔名, 而多按 shift 也很討厭, 你們用 GUI 的都不知道打檔名的辛苦。

然後只能有一個 public class, 挪, 就像 PrintString.java 那樣。
PrintString.java
1 package DescentPackage;
2 
3 public class PrintString
4 {
5   public void msg()
6   {
7     System.out.println("XX Hello World");
8   }
9 }

程式寫好了又來到頭痛的編譯問題, 這要怎麼編譯呢?

可以在 DescentPackage/ 上一層編譯

cent@debian64:java$ pwd
/home/descent/git/progs/java
descent@debian64:java$ ls
DescentPackage/
$ javac DescentPackage/HelloWorld.java

也可以在 DescentPackage/ 這層編譯

descent@debian64:DescentPackage$ pwd
/media/work/git/progs/java/DescentPackage
descent@debian64:DescentPackage$ javac -cp .. HelloWorld.java 

比 c++ 聰明的是, javac 會把相關的 java source code 也編譯成 .class, 所以打一次 javac 會得到 HelloWorld.class PrintString.class 2 個 class 檔案。

編譯完成, 再來是頭痛的執行問題:

descent@debian64:java$ pwd
/home/descent/git/progs/java
descent@debian64:java$ java  DescentPackage.HelloWorld 
yy Hello World
XX Hello World
descent@debian64:java$ cd DescentPackage/
descent@debian64:DescentPackage$ java -cp .. DescentPackage.HelloWorld 
yy Hello World
XX Hello World

這是很簡單的範例, 如果使用多個 package namespace, 就得好好檢視每個 class 是不是都可被搜尋到。

javac, java man page
1 javac [ options ] [ sourcefiles ] [ classes] [ @argfiles ]
2
3 java [options] classname [args]
4 java [options] -jar filename [args]

要針對 java 檔案來執行程式, 得用 jar 的方式, 下回我們來挑戰 jar。

2017年4月14日 星期五

java 編譯/執行環境 (0) - 單獨一個 java source code 檔案

寫完一個 java 程式後, 我想知道如何編譯它、如何執行它。哥是正統 unix 軟體開發人員, 沒在用 eclipse 這種按下去就編好的東西, 只用會讓自己難過的 command line 編譯方式。不過 java 的編譯還真不是普通的複雜, 如何執行它更是混亂。

list 1. HelloWorld.java
1
2 public class HelloWorld
3 {
4   public static void main(String[] args)
5   {
6     System.out.println("Hello World");
7   }
8 }

list 2. HelloWorld.class
1 descent@debian64:DescentPackage$ pwd
2 /media/work/git/progs/java/DescentPackage
3 descent@debian64:DescentPackage$ javac HelloWorld.java 
4 descent@debian64:DescentPackage$ ls -l HelloWorld.class
5 -rw-r--r-- 1 descent descent 425 Mar 30 09:44 HelloWorld.class
6 descent@debian64:DescentPackage$ java HelloWorld 
7 Hello World

看來好像沒什麼難的是吧? list 2 L6 看起來好像是把 HelloWorld.class 執行起來, 但 java HelloWorld 的 HelloWorld 並不是 HelloWorld.class 這個檔案, 實際上是 ...

先賣個關子, 加上 package namespace 試試。

HelloWorld.java
 1 package DescentPackage;
 3
 4 public class HelloWorld
 5 {
 6   public static void main(String[] args)
 7   {
 8     System.out.println("Hello World");
 9   }
10 }

啊哈, 可以用 javac 編譯出 HelloWorld.class

descent@debian64:DescentPackage$ javac HelloWorld.java 
descent@debian64:DescentPackage$ java HelloWorld 
Error: Could not find or load main class HelloWorld

但用 java 這個指令竟然無法執行成功, 亂試 ...

descent@debian64:DescentPackage$ java DescentPackage.HelloWorld
Error: Could not find or load main class DescentPackage.HelloWorld

果然還是不行,

descent@debian64:DescentPackage$ cd ..
descent@debian64:java$ ls DescentPackage/
a.jar             HelloWorld.java       MANIFEST.MF
HelloWorld.class  HelloWorld.java.html  PrintString.java
HelloWorld.jar    makefile              readme
descent@debian64:java$ java DescentPackage.HelloWorld
Hello World

疑, 可以執行了。但要在上一層才能執行覺得很不爽, 我就想在 DescentPackage/ 目錄中執行, 難道不可以嗎?

descent@debian64:java$ cd DescentPackage/
descent@debian64:DescentPackage$ java DescentPackage.HelloWorld
descent@debian64:DescentPackage$ java -cp .. DescentPackage.HelloWorld
Hello World

這樣應該有點感覺了吧! java 後面接的不是編譯出來的檔名, 而是 main class, 而在 package 裡頭的 main class 自然要加上 namespace, 也就是 DescentPackage.HelloWorld。這是第一個條件。

javac, java man page
1 javac [ options ] [ sourcefiles ] [ classes] [ @argfiles ]
2
3 java [options] classname [args]
4 java [options] -jar filename [args]

第二個條件就是 classpath, 要從哪裡開始找到這個 DescentPackage.HelloWorld main class,

java DescentPackage.HelloWorld

會從目前目錄開始找 HelloWorld.class 這個檔案, 如果目前目錄是 cc, 會從這裡往 DescentPackage/ (也就是 cc/DescentPackage/) 找 HelloWorld.class 這個檔案, 自然找不到, 建立一個 DescentPackage, 把 HelloWorld.class move 到 DescentPackage, java DescentPackage.HelloWorld 又成功執行了。

descent@debian64:DescentPackage$ mkdir DescentPackage
descent@debian64:DescentPackage$ mv HelloWorld.class DescentPackage/
descent@debian64:DescentPackage$ java DescentPackage.HelloWorld
Hello World
descent@debian64:DescentPackage$ pwd
/media/work/git/progs/java/DescentPackage
descent@debian64:DescentPackage$ ls
a.jar           HelloWorld.jar   HelloWorld.java.html  MANIFEST.MF       readme
DescentPackage  HelloWorld.java  makefile              PrintString.java
descent@debian64:DescentPackage$ ls DescentPackage/
HelloWorld.class


Java 編程語言 (The Java Programming Language, 4/e)
Ken Arnold、James Gosling、David Holmes 著
侯捷、柯向上 譯
那如果我就是要在 DescentPackage 目錄執行呢? 行! 告訴 java, 從上一層開始找 HelloWorld.class 就可以了。這是為什麼加上 -cp .. 的原因。

必須要知道 package name 和 classpath 才能正確執行 java 程式, 這是一個比較複雜的地方。

Java 編程語言 (The Java Programming Language, 4/e) 這本書完全沒寫到這個, 我覺得很不舒服, 如果連寫出來的程式都不知道怎麼編譯與執行, 還怎麼學這個程式語言呢?

ref: