0%

try catch 是 java 用來補捉例外狀況的語法,bash 腳本本身並不支援這種語法,但是我們可以透過特殊的方法來達到同樣的目的。

例如:

1
2
3
4
5
6
7
8
9
10
{ # try

command1 &&
command2 &&
command3

} || { # catch
# save log for exception
echo 執行異常
}

在上面的範例當中,當 command1, command2 發生錯誤時,後續的指令都不會執行,並會直接跳到 echo 執行異常 這一行。

而且當 command1, command2, command3 都順利執行後,echo 執行異常 並不會被觸發。

程式會這樣運作是因為 or 運算元(||)會判斷第一個括號({...})內回傳的值是不是 True,若是 True 的話,第二個括號內的指令就不會執行。

指令順利執行完並結束可以視為 True,所以第一個括號內的三個指令才需要用 and 運算元(&&)串接在一起,因為 True AND True AND True == True。

如果把 && 刪掉的話,command1, command2, command3 都會執行,且如果 command3 執行異常的話,echo 執行異常 也會被觸發。

問題描述

有時候我們在用 python 開發專案時,可能 A 專案需要 C 模組的 1.0 版,B 專案要 C 模組的 2.0 版,如果你要在 A、B 專案之間切換就會非常不方便。

當然你可以在開發 A 專案時,把 C 模組 2.0 版刪掉,然後改裝 1.0 版,等要處理 B 專案時,再把 C 模組升級到 2.0 版,不過這樣操作太過麻煩。

virtualenv 的誕生就是為了解決這個問題。

安裝方式

virtualenv 可透過 pip 安裝,輸入 pip install virtualenv 就能安裝,其它安裝方法可參考 virtualenv installation

使用方式

首先切換到打算建立 python 環境的目錄,並輸入 virtualenv <env_name>,例如我想建名叫 myenv 的 python 環境,我可以輸入 virtualenv myenv,輸入完就會產生 myenv/ 資料夾,這時可以用 source myenv/bin/activate 來啟動環境。

啟動後你可以用 pip freeze 來確認是不是真的沒有任何已裝好的 python 模組,因為全新的環境不應該有任何已安裝的模組。

這時你就可以將要安裝的套件寫到 requirements.txt 檔案裡,然後用 pip install -r requirements.txt 安裝。

requirements.txt 內容長的像這樣:

1
2
3
4
boto3==1.9.253
pandas==1.0.3
requests==2.22.0
PyYAML==3.13

問題介紹

AWS Lambda 是一套 Serveless 服務,讓使用者不必花太多時間佈建或管理伺服器,同時得到更多時間編寫核心邏輯。

不過 AWS Lambda 也有些小缺點,例如當 code 與用到的 libraries 超過 3MB 時,你會被告知 deployment package too large

upload successful

雖然這不影響 function 的運行,但會讓人不方便編輯與查看 lambda function 內容。

解決方法

為了解決這項問題,AWS 在 2018 年推出 Lambda Layers 來存放 function 之間共用的 libraries。

實際舉例

以下拿 python3 的 Lambda Function 當例子:

假設有個 python 程式需要 PyMySQLPyYAMLopenpyxl 三個 Libraries。

使用 Layer 前

在不使用 layer 的情況下,你需要建立一個 requirements.txt,並在裡面寫上 Libraries 與對應的版本。

upload successful

然後用 pip install --requirement requirements.txt 安裝 Libraries。裝好後你會發現資料夾裡多了好多不是自己寫的檔案:

upload successful

由於這些 libraries 跟你寫的 code 在同一個資料夾層級,它們會阻礙你閱讀 code,而且它們超過 3MB,無法在 AWS Lambda 線上編輯器顯示。

使用 Layer 後

當你打算採用 layer 時,你需要將 Libraries 打包到名為 python 的資料夾內

1
2
3
pip install \
--requirement requirements.txt \
--target python

然後用 zip -r lambdaLayer.zip python 將它打包再上傳。

以下介紹透過 AWS Console 上傳的方式

首先進入 AWS Lambda Function Layers 頁面,並點選 Create Layer

upload successful

上傳剛剛打包好的 zip 檔

upload successful

上傳好你會得到一串資源識別碼 (ARN, Amazon Resource Name),記下它,因為等一下會用到

upload successful

這時再回到你想加入 Layer 的 lambda function,然後點選 Layers。注意 Layers 右邊有個 (0),它表示現在有的 Layer 數是 0。

upload successful

然後再點選 Add a layer

upload successful

再來選 Provide a layer version ARN,並貼上剛剛記下的 ARN

upload successful

這時 Layer 只快建立完成,最後只要按下 Save 就能儲存 Layer,注意這時 Layers 右邊的數字變成 1,表示有一個 Layer。

upload successful

結果比較

從上面的例子可以發現使用 Layer 後,編輯器裡只會留下你自己編寫的檔案

uploaded!

這樣不僅方便看 code,也可以在 AWS Lambda 線上編輯器直接修改 code 。

0800 開頭的電話是受話方付費。假設你打電話給信用卡公司的 0800 專線,信用卡公司就要付錢給電信業者。

手機由於通話費用較高的關係,有些公司會設定只能透過市話撥打 0800 專線,手機必須撥打另外設立的市內電話,並自行負擔費用。

幸好手機可以改用 Skype 撥打 0800 免付費電話,不用買月租,不必儲值。記得用的時候手機必須連上網路,不管是 4G 或 Wifi 上網都可以。

#省錢 #小資 #免費

對於遠端 ssh 登入 Linux 的使用者來說,若遇到程式需要跑很久的情況(例如 scp 傳輸大檔案),通常只能乖乖等它執行完,若跑到一半不幸連線中斷,程式只能重跑一次,造成大量的時間被浪費。

幸好 Linux 有提供 nohup 指令讓我們可以在 ssh 連線登出後,程式照樣能繼續執行。

一般 Linux 使用者登出後,他執行的所有程式都會收到 SIGHUP (SIGnup Hang UP)訊號,正常程式收到 SIGHUP 就會立刻停止執行。

nohup (字面意思 No Hang Up)可以讓程式忽略 SIGHUP,所以當使用者斷線或登出時,程式可以繼續執行,不會受到影響。

使用方法

假設原本你想用 scp /path/to/my/file brian@123.123.123.123:/home/brian 將一個大檔案複製到遠端機器,你可以將指令改成 nohup scp /path/to/my/file brian@123.123.123.123:/home/brian & 來執行,指令尾端的 & 代表nohup 會在背景,避免卡住你現在執行的 ssh 視窗。

觀察輸出結果

既然有程式在執行,那麼你一定會想看執行的結果。 nohup 預設會將所有的輸出資訊寫到 nohup.txt 這個檔案,你可以用 cat nohup.txt 查看程式輸出資訊,或是用 tail -f nohup.txt 即時查看最後輸出的幾筆資料。

One of the common issues we face when developing a software is dealing with dates and times. For example, after getting a date-time string from an API, we might have to convert it to another format that is easier for humans to read. If the API executes in different places around the world, we will need to take timezone into consideration.

Here, I summarize some of the frequently used functions when handling python datetime.

Python datetime conversion

Let’s first talk about the conversion between string and datetime:

Converting string to datetime

Use strptime() if your want to convert a string to datetime:

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/python3

from datetime import datetime

format = '%Y-%m-%d %H:%M:%S %z'
# the string variable
datetime_str = '2018-05-13 12:34:56 +0800'
# the `datetime` variable
datetime_dt = datetime.strptime(datetime_str, format)

print(type(datetime_dt)) # <class 'datetime.datetime'>
print(datetime_dt) # 2018-05-13 12:34:56+08:00

The meaning of the format codes can be found at Python’s documentation.

Converting datetime to string

Use strftime() if your want to convert a datetime to string:

1
2
3
4
5
6
7
8
9
#!/usr/bin/python3

from datetime import datetime

datetime_dt = datetime.now() # 2018-08-24 18:00:25.855212
datetime_str = datetime_dt.strftime('%Y-%m-%d %H:%M:%S %z')

print(type(datetime_str)) # <class 'str'>
print(datetime_str) # 2018-08-24 18:00:25

Changing timezone:

astimezone() can be used to change the timezone of a datetime variable:

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/python3

import pytz
from datetime import datetime

# My PC is located in UTC+8 timezone
datetime_dt = datetime.now() # 2018-08-24 21:35:46.694889
print(datetime_dt)

datetime_taipei_dt = datetime_dt.astimezone(pytz.timezone('Asia/Tokyo')) # UTC+9

print(datetime_taipei_dt) # 2018-08-24 22:35:46.694889+09:00

Because my PC is located in UTC+8 timezone, the content of datetime_dt represents the time of UTC+8 timezone. Tokyo is located in UTC+9 timezone. That’s why there’s a 1 hour difference between datetime_dt and datetime_taipei_dt.

AWS CLI(command line interface) 的使用過程中,可能需要在不同的配置之間做切換,例如從 production 轉換到 development 環境。在 ~/.aws/config~/.aws/credentials 設定好後,基本的切換方式包含

  1. 在 AWS CLI 後面加上 --profile user1 即可使用 user1 profile。
    例如

    1
    2
    $ aws s3 ls --profile user1
    2018-06-11 20:45:20 test-bucket
  2. 設定 AWS_DEFAULT_PROFILE,例如 export AWS_DEFAULT_PROFILE=user_name。設定好了 AWS CLI 就會使用該帳號。

上述方法切換前,都需要改動 Bash Profile,實在不方便,底下有較便捷的方式:

  1. 使用 alias:

    例如設定 alias aws1='aws --profile user1'alis aws2='aws --profile user2',這樣一來,以後輸入 aws1 s3 ls 就會看到 user1 的 s3 bucket,aws2 s3 ls 會看到 user2 的。

  2. 改用 oh-my-zsh 並搭配 AWS plugin

    裝好後可以用 aspagp 來切換與顯示 AWS CLI 配置。

參考資料:

Promise.all is a useful funciton when you want to wait for more than 1 promise running in parallel to complete. However, sometimes you may want to limit the number of parallel promises. For example, you may wish to limit the number of simultaneous downloads from a website.

cwait is a convenient tool which allows you to limit the number of promises running in parallel.

Here I’ll demonstrate how to use cwait. First let’s assume we have a function later that returns a promise which is resolved after a specified timeout.

function later (time, val) {
  console.log(`later is triggered. ${val}`)
  return new Promise((resolve, reject) => {
    setTimeout(resolve, time, val)
  })
}

When we create 10 promises with later, and execute them with Promise.all, it will take about 1 second to finish the execution.

// echoNumberPromiseAll.js

const Promise = require('bluebird')

let totalNumber = 10
let timeout = 1000 // 1000 ms
let start, finish

function later (time, val) {
  console.log(`later triggered. ${val}`)
  return new Promise((resolve, reject) => {
    setTimeout(resolve, time, val)
  })
}

let promises = []
function init () {
  start = new Date()
  for (let i = 0; i < totalNumber; i += 1) {
    promises.push(later(timeout, i))
  }
}

init()

Promise.all(promises)
  .then((result) => {
    console.log(`All done. result: ${result}`)
    finish = new Date()
    console.log(`Execution time: ${finish - start} ms`)
  })
  .catch(err => {
    console.error(`error: ${err}`)
  })

node echoNumberPromiseAll.js‘s execution result:

node echoNumberPromiseAll.js
later triggered. 0
later triggered. 1
later triggered. 2
later triggered. 3
later triggered. 4
later triggered. 5
later triggered. 6
later triggered. 7
later triggered. 8
later triggered. 9
All done. result: 0,1,2,3,4,5,6,7,8,9
Execution time: 1012 ms

As you can see from the above output, 10 promises are triggered simultaneously. Because the execution time is about 1 second.

Before we can apply cwait, we have to convert Promise.all to Promise.map first.

Here’s the converted code:

// echoNumberPromiseMap.js

const Promise = require('bluebird')

let totalNumber = 10
let timeout = 1000 // 1000 ms
let start, finish

let numbers = []

function later (time, val) {
  console.log(`later triggered. ${val}`)
  return new Promise((resolve, reject) => {
    setTimeout(resolve, time, val)
  })
}

function init () {
  start = new Date()
  for (let i = 0; i < totalNumber; i += 1) {
    numbers.push(i)
  }
}

function promiseMap (number) {
  return later(timeout, number)
}

init()

Promise.map(numbers, promiseMap)
  .then((result) => {
    console.log(`All done. result: ${result}`)
    finish = new Date()
    console.log(`Execution time: ${finish - start} ms`)
  })
  .catch(err => {
    console.error(`error: ${err}`)
  })

Then we will be able to apply cwait to the above code:

// echoNumberCwait.js

const TaskQueue = require('cwait').TaskQueue
const Promise = require('bluebird')

let queue = new TaskQueue(Promise, 3)

let totalNumber = 10
let timeout = 1000 // 1000 ms
let start, finish

let numbers = []

function later (time, val) {
  console.log(`later triggered. ${val}`)
  return new Promise((resolve, reject) => {
    setTimeout(resolve, time, val)
  })
}

function init () {
  start = new Date()
  for (let i = 0; i < totalNumber; i += 1) {
    numbers.push(i)
  }
}

function promiseMap (number) {
  return later(timeout, number)
}

init()

Promise.map(numbers, queue.wrap(promiseMap))
  .then((result) => {
    console.log(`All done. result: ${result}`)
    finish = new Date()
    console.log(`Execution time: ${finish - start}`)
  })
  .catch(err => {
    console.error(`error: ${err}`)
  })

In the example above, we limit the number of parallel promises to 3. Here’s the execution result of node echoNumberCwait.js

later triggered. 0
later triggered. 1
later triggered. 2
later triggered. 3
later triggered. 9
later triggered. 8
later triggered. 7
later triggered. 6
later triggered. 5
later triggered. 4
All done. result: 0,1,2,3,4,5,6,7,8,9
Execution time: 4016 ms

The program first executes the promises with val equal to 0, 1, and 2. And waits until the 3 promises are completed. Then it executes the promises with val equal to 3, 9, and 8. and wait for them to complete …

So in the end, it takes about 4 seconds to finish the execution.

進化:從孤膽極客到高效團隊是一本談軟體工程師之間如何合作的書,書中提到的心法對於需要團隊合作的工程師非常受用。例如有一章提到在軟體開發過程中,隱藏想法、獨自工作是相當有害,會增加失敗機率,且還會錯失成長機會。

書中舉了自行車設計當例子說明:

假設你是一位自行車設計的愛好者,當你某天想出了一種全新的變速器設計。於是你訂購了零件,躲在車庫花了好幾週打造雛形。當有位同樣是自行車愛好者的鄰居問你在做什麼時,你決定不告訴他,想等到成品完成時再宣布。又過了幾個月,雛型還沒設計完成,因為你遇到了困難,但由於你不和其他人討論這個點子,你也無法向懂機械的朋友尋求意見。

然後有一天,鄰居從他的車庫裡推出一輛自行車,車上裝有一種全新的變速器。原來,你的鄰居也在研究類似的裝置,但他得到了在自行車店工作的朋友的幫助。這時你終於忍不住了,向他展示了你的設計。然後鄰居指出你的設計中存在的一些簡單缺陷。如果早點和他溝通的話,這些問題在工作開始的第一週就可以獲得修正。

從這個例子我們可以發現,如果將想法秘而不宣,堅持等一切盡善盡美才公布,那麼你就是在進行一場代價高昂的賭博。在專案早期很容易出現基本的設計錯誤,你可能會重複設計已存在的東西,同時還放棄了合作的好處。

英文跟中文都是人類用來溝通的語言,只是他們的起源不同,說起來的方式也會跟著不同。

例如在傳統的英文教學裡,當你要學會底下這句話在表達什麼時,可能是這麼教的:

The man who is talking is my brother.那個正在講話的人是我哥。

首先老師會跟你提到這兩個句子,

  1. The man is talking.那個人正在講話
  2. The man is my brother.那個人是我哥

然後跟你說有個叫「關係子句」的東西,它的用途是把一個完整的句子,放在另一句話中,當成形容詞用。

所以把第二句話變成形容詞合併到第一句就會變成 The man who is talking is my brother.

其實根本就不用知道什麼叫關係子句,因為當我們看到 The man who is talking is my brother. 與它的中文翻譯那個正在講話的人是我哥。時,我們可以很直覺地先將英文翻成

那個人(正在講話)是我哥。

這句中文唸起來很奇怪,好像「正在講話」是要在心裡唸,嘴巴不能講出來的感覺。但一般人還是可以理解它的意思,只要再將順序換一下句子就會順暢

那個正在講話的人是我哥。

如此一來,我們頭腦裡就建立出英文對應到中文的規則,透過這種方式,我們可以利用推理來學會英文文法。