從 Android.mk 到 Android.bp

本篇介紹一下 Android 的編譯工具發展歷史,從 Android.mk 至 Android.bp,以及對應的 Android 版本各發生了什麼事,最後介紹一下要如何從 Android.mk 轉換成 Android.bp。

工具發展歷史

早期的 Android 系統都是使用 Android.mk 的來編譯原始碼,由於 Google 發現原始碼越來越大,編譯越來越慢,從 Android 7 Nougat 開始導入 Android.bp。所以 Android.bp 是準備取代 Android.mk 的。

Android 版本編譯工具的發展歷史:
從 platform/system/core/ 目錄以及 sdcard、base、liblog 模組裡的變化,可以看出 Google 是在逐漸地把 Android.mk 替換成 Android.bp
Android 6 Marshmallow
使用 Android.mk
platform/system/core/
platform/system/core/sdcard/Android.mk
platform/system/core/base/Android.mk
platform/system/core/liblog/Android.mk
Android 7 Nougat
導入 ninja 和 kati
platform/system/core/
platform/system/core/sdcard/Android.mk
platform/system/core/base/Android.mk
platform/system/core/liblog/Android.bp
Android 8 Oreo
使用 Android.bp 來替換 Android.mk,導入 Soong
platform/system/core/
platform/system/core/sdcard/Android.mk
platform/system/core/base/Android.bp
platform/system/core/liblog/Android.bp
Android 9 Pie
強制使用 Android.bp (有強制嗎?)
platform/system/core/
platform/system/core/sdcard/Android.bp
platform/system/core/base/Android.bp
platform/system/core/liblog/Android.bp
Android 10

一些常見模組的路徑都在 platform/system/core/ 下:
adb
base
fastboot
libcutils
liblog
logcat
logd

Android.bp to Android.bp 轉換工具

使用 Google 提供的 androidmk 這個轉換小工具可以將 Android.mk 轉換成 Android.bp。

1
$ androidmk Android.mk > Android.bp

參考
[1] 理解Android.bp - Gityuan博客 | 袁輝輝的技術博客
http://gityuan.com/2018/06/02/android-bp/
[2] Android編譯系統中的Android.bp、Blueprint與Soong ‧ 零壹軒‧筆記
https://note.qidong.name/2017/08/android-blueprint/
[3] 【Bash百宝箱】从Android.mk到Android.bp - evo
https://blog.csdn.net/ieearth/article/details/54707416
[4] aosp-mirror/platform_system_core - github
https://github.com/aosp-mirror/platform_system_core

Android.mk 的運作原理

本篇記錄一下 Android.mk 的運作原理與編譯流程。

Android.mk 的編譯原理

在Android平台項目任意一個合適的目錄下,創建一個Android.mk文件,都可以新增一個模塊(Module)。

滿足上述邏輯的Android.mk,都會被include到Android的Makefile中。 但是,其中定義的模塊是否參與編譯,則未必會被編譯。

參考
[1] Android.mk的深入介绍
https://note.qidong.name/2017/08/android-mk/
[2] 理解 Android Build 系統
https://www.ibm.com/developerworks/cn/opensource/os-cn-android-build/

如何寫 Android.mk

本篇記錄一下如何寫 Android.mk,雖然 Google 已經開始全面地改用 Android.bp,但這段過渡期可能還需幾年的時間,Android.mk 的了解以及如何撰寫還是值得學習的。

從 Android 7 Nougat 開始逐漸地改用 Android.bp

如何寫 Android.mk

首先先清理變數

1
2
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

指定模組名與模組變數

LOCAL_MODULE︰模組名稱,這名稱必須是唯一,不能和既有模組相同。 如果 LOCAL_MODULE 未設定,則使用LOCAL_PACKAGE_NAME。 如果再沒有,就會編譯失敗。例如模組名叫做hello,那麼就可以透過 make hello 指令直接編譯hello這個模組。
LOCAL_SRC_FILES︰原始碼檔案列表

引用編譯規則

1
include $(BUILD_EXECUTABLE)

以下這些用來給Android.mk引用的文件及其變數,定義在build/core/config.mk中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
BUILD_COMBOS:= $(BUILD_SYSTEM)/combo

CLEAR_VARS:= $(BUILD_SYSTEM)/clear_vars.mk
BUILD_HOST_STATIC_LIBRARY:= $(BUILD_SYSTEM)/host_static_library.mk
BUILD_HOST_SHARED_LIBRARY:= $(BUILD_SYSTEM)/host_shared_library.mk
BUILD_STATIC_LIBRARY:= $(BUILD_SYSTEM)/static_library.mk
BUILD_SHARED_LIBRARY:= $(BUILD_SYSTEM)/shared_library.mk
BUILD_EXECUTABLE:= $(BUILD_SYSTEM)/executable.mk
BUILD_HOST_EXECUTABLE:= $(BUILD_SYSTEM)/host_executable.mk
BUILD_PACKAGE:= $(BUILD_SYSTEM)/package.mk
BUILD_PHONY_PACKAGE:= $(BUILD_SYSTEM)/phony_package.mk
BUILD_HOST_PREBUILT:= $(BUILD_SYSTEM)/host_prebuilt.mk
BUILD_PREBUILT:= $(BUILD_SYSTEM)/prebuilt.mk
BUILD_MULTI_PREBUILT:= $(BUILD_SYSTEM)/multi_prebuilt.mk
BUILD_JAVA_LIBRARY:= $(BUILD_SYSTEM)/java_library.mk
BUILD_STATIC_JAVA_LIBRARY:= $(BUILD_SYSTEM)/static_java_library.mk
BUILD_HOST_JAVA_LIBRARY:= $(BUILD_SYSTEM)/host_java_library.mk
BUILD_DROIDDOC:= $(BUILD_SYSTEM)/droiddoc.mk
BUILD_COPY_HEADERS := $(BUILD_SYSTEM)/copy_headers.mk
BUILD_NATIVE_TEST := $(BUILD_SYSTEM)/native_test.mk
BUILD_NATIVE_BENCHMARK := $(BUILD_SYSTEM)/native_benchmark.mk
BUILD_HOST_NATIVE_TEST := $(BUILD_SYSTEM)/host_native_test.mk

BUILD_SHARED_TEST_LIBRARY := $(BUILD_SYSTEM)/shared_test_lib.mk
BUILD_HOST_SHARED_TEST_LIBRARY := $(BUILD_SYSTEM)/host_shared_test_lib.mk
BUILD_STATIC_TEST_LIBRARY := $(BUILD_SYSTEM)/static_test_lib.mk
BUILD_HOST_STATIC_TEST_LIBRARY := $(BUILD_SYSTEM)/host_static_test_lib.mk

BUILD_NOTICE_FILE := $(BUILD_SYSTEM)/notice_files.mk
BUILD_HOST_DALVIK_JAVA_LIBRARY := $(BUILD_SYSTEM)/host_dalvik_java_library.mk
BUILD_HOST_DALVIK_STATIC_JAVA_LIBRARY := $(BUILD_SYSTEM)/host_dalvik_static_java_library.mk

範例:編譯一個可執行檔

模組名稱為 hellowrold,編譯出一個 hellowrold 的執行檔。

1
2
3
4
5
6
7
8
9
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_SRC_FILES := main.cpp
LOCAL_MODULE := hellowrold
LOCAL_CFLAGS := -Wall -Wno-unused-parameter -Werror
LOCAL_SHARED_LIBRARIES :=

include $(BUILD_EXECUTABLE)

範例:編譯一個靜態函式庫

模組名稱為 libfoo,編譯出一個 libfoo.a 的靜態函式庫。

1
2
3
4
5
6
7
8
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE := libfoo
LOCAL_SRC_FILES := aaa.cpp
LOCAL_CFLAGS := -Wall -Wno-unused-parameter -Werror

include $(BUILD_STATIC_LIBRARY)

範例:編譯一個動態函式庫

模組名稱為 libfoo,編譯出一個 libfoo.so 的動態函式庫。

1
2
3
4
5
6
7
8
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE := libfoo
LOCAL_SRC_FILES := aaa.cpp
LOCAL_CFLAGS := -Wall -Wno-unused-parameter -Werror

include $(BUILD_SHARED_LIBRARY)

範例:編譯一個 APK 文件

1
2
3
4
5
6
7
8
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_SRC_FILES := $(call all-subdir-java-files) # 獲取所有子目錄中的 Java 文件
LOCAL_STATIC_JAVA_LIBRARIES := static-library # 當前模塊依賴的靜態 Java 庫,如果有多個以空格分隔
LOCAL_PACKAGE_NAME := LocalPackage # 當前模塊的名稱

include $(BUILD_PACKAGE) # 編譯 APK 文件

範例:編譯一個 Java 的靜態函式庫

1
2
3
4
5
6
7
8
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_SRC_FILES := $(call all-subdir-java-files) # 獲取所有子目錄中的 Java 文件
LOCAL_JAVA_LIBRARIES := android.test.runner # 當前模塊依賴的動態 Java 庫名稱
LOCAL_MODULE := sample # 當前模塊的名稱

include $(BUILD_STATIC_JAVA_LIBRARY) # 將當前模塊編譯成一個靜態的 Java 庫

下表格為常用與常見的編譯規則

引用編譯規則變數 檔名 說明
BUILD_EXECUTABLE executable.mk 編譯成裝置上的可執行檔
BUILD_STATIC_LIBRARY static_library.mk 編譯成裝置上的靜態函式庫
BUILD_SHARED_LIBRARY shared_library.mk 編譯成裝置上的動態函式庫
BUILD_PREBUILT prebuilt.mk 處理一個預編譯好的檔案 (例如.a, .so, .jar)
BUILD_PACKAGE package.mk 編譯成apk

參考
[1] Android.mk的深入介绍
https://note.qidong.name/2017/08/android-mk/
[2] 理解 Android Build 系統
https://www.ibm.com/developerworks/cn/opensource/os-cn-android-build/

如何看 Android Platform 的 PLATFORM_VERSION 版本號碼?

本篇紀錄一下如何看 Android Platform (BSP) 的 PLATFORM_VERSION 版本號碼。
PLATFORM_VERSION 位在 platform/build/core/version_defaults.mk 裡。

Master
https://android.googlesource.com/platform/build/+/refs/heads/master/core/version_defaults.mk

Android 10
https://android.googlesource.com/platform/build/+/refs/tags/android-10.0.0_r17/core/version_defaults.mk
https://android.googlesource.com/platform/build/+/refs/heads/android10-release/core/version_defaults.mk
PLATFORM_VERSION.QP1A := 10

Android 9 Pie
https://android.googlesource.com/platform/build/+/refs/tags/android-9.0.0_r51/core/version_defaults.mk
https://android.googlesource.com/platform/build/+/refs/heads/pie-release/core/version_defaults.mk
PLATFORM_VERSION.PPR1 := 9

Android 8 Oreo
https://android.googlesource.com/platform/build/+/refs/tags/android-8.0.0_r41/core/version_defaults.mk
PLATFORM_VERSION.OPR1 := 8.0.0
https://android.googlesource.com/platform/build/+/refs/heads/oreo-mr1-release/core/version_defaults.mk
PLATFORM_VERSION.OPM1 := 8.1.0

Android 7 Nougat
https://android.googlesource.com/platform/build/+/refs/heads/nougat-release/core/version_defaults.mk
PLATFORM_VERSION := 7.0
https://android.googlesource.com/platform/build/+/refs/heads/nougat-mr2.3-release/core/version_defaults.mk
PLATFORM_VERSION := 7.1.2

Android 6 Marshmallow
https://android.googlesource.com/platform/build/+/refs/heads/marshmallow-release/core/version_defaults.mk
PLATFORM_VERSION := 6.0
https://android.googlesource.com/platform/build/+/refs/heads/marshmallow-mr3-release/core/version_defaults.mk
PLATFORM_VERSION := 6.0.1

Android 5 Lollipop
https://android.googlesource.com/platform/build/+/refs/heads/lollipop-release/core/version_defaults.mk
PLATFORM_VERSION := 5.0.2
https://android.googlesource.com/platform/build/+/refs/heads/lollipop-mr1-cts-release/core/version_defaults.mk
https://android.googlesource.com/platform/build/+/refs/heads/lollipop-mr1-fi-release/core/version_defaults.mk
https://android.googlesource.com/platform/build/+/refs/heads/lollipop-mr1-release/core/version_defaults.mk
PLATFORM_VERSION := 5.1.1

Android 4 KitKat
https://android.googlesource.com/platform/build/+/refs/heads/kitkat-release/core/version_defaults.mk
PLATFORM_VERSION := 4.4
https://android.googlesource.com/platform/build/+/refs/heads/kitkat-mr2.2-release/core/version_defaults.mk
PLATFORM_VERSION := 4.4.4

參考
[1] Where to find the version information in source code on Android platform - Stack Overflow
https://stackoverflow.com/questions/2377225/where-to-find-the-version-information-in-source-code-on-android-platform
[2] Android版本列表 - 維基百科,自由的百科全書
https://zh.wikipedia.org/wiki/Android%E7%89%88%E6%9C%AC%E5%88%97%E8%A1%A8

在 Ubuntu 下編譯安裝 OpenCV 4.1.2

本篇 ShengYu 將介紹如何在 Ubuntu 下編譯安裝 OpenCV 4.1.2,
這版 OpenCV 4.1.2 釋出日期是 2019/10/10,開始動手編譯原始碼安裝吧!

以下為我的系統環境:
作業系統:Ubuntu 16.04
GCC:5.4.0 (Ubuntu 5.4.0-6ubuntu1~16.04.11)
G++:5.4.0 (Ubuntu 5.4.0-6ubuntu1~16.04.11)
使用版本:OpenCV 4.1.2 下載處 / zip

安裝編譯所需的套件

安裝編譯 opencv 所需要的套件,gcc / g++ / make / libc6-dev 都已包含在 build-essential 裡了,
所以不計較空間的話安裝 build-essential 算是相當的簡單省事。

1
sudo apt-get install build-essential cmake

下載原始碼

使用 wget 指令下載原始碼,並且解壓縮。

1
2
wget https://github.com/opencv/opencv/archive/4.1.2.zip
unzip 4.1.2.zip

編譯安裝

切換到剛剛解壓縮出來的目錄開始進行編譯,
最後 make 時,就看你電腦 CPU 有幾個核心,帶不同的參數,有助於加快整個編譯時間。

1
2
3
4
cd opencv-4.1.2
mkdir -p build && cd build
cmake ..
make -j4

安裝,

1
sudo make install

重新載入動態連結,

1
sudo ldconfig -v

查詢已安裝的 opencv 版本

使用 opencv_version 指令查詢已安裝 opencv 的版本。

1
2
$ opencv_version
4.1.2

或者使用 pkg-config 查詢已安裝的 opencv 版本

1
2
$ pkg-config --modversion opencv
4.1.2

到這裡就完成 opencv 的安裝了,下篇將會介紹 如何寫第一支 OpenCV 程式

相關主題
在 Ubuntu 下寫第一支 OpenCV 程式
怎麼查詢 OpenCV 的版本

在 Ubuntu 下編譯安裝 OpenCV 3.4.8

本篇 ShengYu 將介紹如何在 Ubuntu 下編譯安裝 OpenCV 3.4.8,
這版 OpenCV 3.4.8 釋出日期是 2019/10/09,開始動手編譯原始碼安裝吧!

以下為我的系統環境:
作業系統:Ubuntu 16.04
GCC:5.4.0 (Ubuntu 5.4.0-6ubuntu1~16.04.11)
G++:5.4.0 (Ubuntu 5.4.0-6ubuntu1~16.04.11)
使用版本:OpenCV 3.4.8 下載處 / zip

安裝編譯所需的套件

安裝編譯 opencv 所需要的套件,gcc / g++ / make / libc6-dev 都已包含在 build-essential 裡了,
所以不計較空間的話安裝 build-essential 算是相當的簡單省事。

1
sudo apt-get install build-essential cmake

下載原始碼

使用 wget 指令下載原始碼,並且解壓縮。

1
2
wget https://github.com/opencv/opencv/archive/3.4.8.zip
unzip 3.4.8.zip

編譯安裝

切換到剛剛解壓縮出來的目錄開始進行編譯,
最後 make 時,就看你電腦 CPU 有幾個核心,帶不同的參數,有助於加快整個編譯時間。

1
2
3
4
cd opencv-3.4.8
mkdir -p build && cd build
cmake ..
make -j4

安裝,

1
sudo make install

重新載入動態連結,

1
sudo ldconfig -v

查詢已安裝的 opencv 版本

使用 opencv_version 指令查詢已安裝 opencv 的版本。

1
2
$ opencv_version
3.4.8

或者使用 pkg-config 查詢已安裝的 opencv 版本

1
2
$ pkg-config --modversion opencv
3.4.8

到這裡就完成 opencv 的安裝了,下篇將會介紹 如何寫第一支 OpenCV 程式

相關主題
在 Ubuntu 下寫第一支 OpenCV 程式
怎麼查詢 OpenCV 的版本

Linux grep/ack/ag 搜尋字串用法與範例

本篇 ShengYu 將介紹如何使用 Linux 下的 grep/ack/ag 指令來搜尋字串,並且將搜尋結果的檔案同時進行移動/複製/刪除。

搜尋目錄下特定樣式的字串

1
2
3
4
5
6
# 使用 grep
grep -rI "foo" *
# 使用 ack (Ack-Grep)
ack "foo"
# 使用 ag (The Silver Srarcher)
ag "foo"

搜尋目錄下檔案內有特定樣式的字串,並且將這些檔案移動mv/複製cp/刪除rm

1
2
3
4
5
# 搜尋包含hello關鍵字的檔案,並且將這些檔案移動到tmp資料夾
$ ack "hello" ./ -l --print0 | xargs -0 -i mv {} tmp

# 刪除包含 hello 關鍵字的檔案
$ ack "hello" ./ -l --print0 | xargs -0 rm

搜尋目錄下檔案內有特定樣式的字串,並且將這些檔案進行取代

1
2
3
# 把目錄下所有檔案文字中包含 "oldtext" 的 取代成 "newtext"  
$ ag oldtext -l0 | xargs -0 sed -i 's/oldtext/newtext/g'
$ grep -rl oldtext . | xargs sed -i 's/oldtext/newtext/g'

其它相關文章推薦
Linux 常用指令教學懶人包
Linux cut 字串處理用法與範例
Linux sed 字串取代用法與範例
Linux find 尋找檔案/尋找資料夾用法與範例

Linux find 尋找檔案/尋找資料夾用法與範例

本篇 ShengYu 將介紹如何使用 Linux find 指令來尋找檔案或尋找資料夾用法與範例,並且 find 找到的檔案同時進行移動mv / 複製cp / 刪除rm / 搜尋grep / 取代sed,學習這些技巧將會大大的提升處理檔案的效率。

以下 Linux find 指令介紹將分為這幾部份,

  • find 基本用法
  • find 找到特定檔案後,並且移動mv這些檔案
  • find 找到特定檔案後,並且移動複製cp這些檔案
  • find 找到特定檔案後,並且移動刪除rm這些檔案
  • find 找到特定檔案後,並且搜尋grep這些檔案內的文字
  • find 找到特定檔案後,並且取代sed這些檔案內的文字
  • find 根據檔案大小來找
  • find 根據檔案修改日期來找
  • find 排除目錄或排除多個目錄
  • find 計算找到的檔案數量

那我們開始吧!

find 基本用法

這邊介紹 find 尋找檔案或尋找資料夾的基本用法,在 Linux 或 macOS 下用 find 指令尋找檔案指令如下,例如要找 hello.txt 檔案的話,find 指令可以這樣用,

1
$ find ./ -name "hello.txt"

如果要不區分大小寫的話,可以用 -iname 這個選項,像這樣用,

1
$ find ./ -iname "hello.txt"

這樣不管是 hello.txt 或 Hello.txt 或 HELLO.txt 都可以找得出來,

如果想用 find 找包含 foo 關鍵字的檔案可以這樣用,

1
$ find ./ -name "*foo*"

但是這樣的結果會找出符合的檔案與資料夾,如果只想找出檔案不想找出資料夾的話可以另外加上 -type 選項,
-type f 是找檔案,-type d 是找資料夾,預設都不加的話就是就是兩種結果都會列出來。

用 find 指令找當下目錄有包含 “foo” 關鍵字的 “檔案” 用法如下,

1
2
3
4
5
$ find ./ -name "*foo*" -type f

# -type f 找檔案
# -type d 找目錄
# 不加 -type 就是兩種結果都會列出來

find 指令找當下目錄有包含 “foo” 關鍵字的 “目錄” 用法如下,

1
$ find ./ -name "*foo*" -type d

find 找到特定檔案後,並且移動mv這些檔案

這邊示範用 find 指令找到特定檔案後,並且移動mv這些檔案,有以下這幾種範例,

1
2
3
4
5
6
7
8
# 把當下目錄有 "foo" 關鍵字檔名移動到 bar 資料夾
$ find ./ -name "*foo*" -type f | xargs -i mv {} bar

# 如果找到的檔案名稱包含空白需加上 -print0
$ find ./ -name "*foo*" -type f -print0 | xargs -0 -i mv {} bar

# 找到C程式檔類型的 並移動到C資料夾
$ file * | grep "C source" | awk '{print $1}' | cut -d':' -f1 | xargs ls | xargs -i mv {} C/

find 找到特定檔案後,並且移動複製cp這些檔案

這邊示範用 find 指令找到特定檔案後,並且複製cp這些檔案,

1
2
# 把當下目錄有 "foo" 關鍵字檔名複製到 bar 資料夾
$ find ./ -name "*foo*" -type f | xargs -i cp {} bar

find 找到特定檔案後,並且移動刪除rm這些檔案

這邊示範用 find 指令找到特定檔案後,並且刪除rm這些檔案,有以下這幾種範例,

1
2
3
4
5
6
7
8
# 把當下目錄有 "foo" 關鍵字檔名給刪除
$ find ./ -name "*foo*" -type f | xargs rm

# 找到 *.mht 檔案 並刪掉(檔案名稱有空白或太長要加 -print0 選項)
$ find ./ -name "*.mht" -type f -print0 | xargs -0 rm

# 找出執行檔類型並刪掉
$ file * | grep "executable" | awk '{print $1}' | cut -d':' -f1 | xargs rm

find 找到特定檔案後,並且搜尋grep這些檔案內的文字

這邊介紹 find 指令找到特定檔案後,並且搜尋grep這些檔案內的文字,有時會需要 find 找到檔案後接著針對這些檔案去作 grep 檔案內的文字搜尋,這時候這個技巧就很實用了,有以下這幾種範例,

1
2
3
4
5
6
7
8
9
10
# 把當下目錄 cpp 檔中的 "main" 關鍵字找出來
$ find ./ -name "*.cpp" -print0 | xargs -0 grep -r "main" --color
$ find ./ -name "*.cpp" -print0 | xargs -0 ag "main"

# 將兩類別關鍵字的檔案 (*.cpp 檔和 *.h 檔) 找出來進行搜尋
$ find ./ \( -name "*.cpp" -o -name "*.h" \) -print0 | xargs -0 grep "stdio"
$ find ./ \( -name "*.cpp" -o -name "*.h" \) -print0 | xargs -0 ag "stdio"

# 找出執行檔類型
$ find -type f -executable -exec file -i '{}' \; | grep 'x-executable; charset=binary'

根據上例子,找到特定檔案後,並且搜尋 grep 這些檔案內的文字,假如有搜尋到特定文字的話在作移動mv / 複製cp / 刪除rm這些檔案,這是進階中的進階技巧了!

1
2
3
4
5
6
7
8
# 把當下目錄 cpp 檔內容中包含 "main" 關鍵字的檔案找出來後移動mv檔案到 out 資料夾
$ find ./ -name "*.cpp" -print0 | xargs -0 grep -rIl "main" | xargs -i mv {} out

# 把當下目錄 cpp 檔內容中包含 "main" 關鍵字的檔案找出來後複製cp檔案到 out 資料夾
$ find ./ -name "*.cpp" -print0 | xargs -0 grep -rIl "main" | xargs -i cp {} out

# 把當下目錄 cpp 檔內容中包含 "main" 關鍵字的檔案找出來後刪除rm
$ find ./ -name "*.cpp" -print0 | xargs -0 grep -rIl "main" | xargs rm

find 找到特定檔案後,並且取代sed這些檔案內的文字

這邊示範用 find 找到特定檔案後,並且取代sed這些檔案內的文字,

1
2
# 把當下目錄 cpp 檔案找出來後用sed將檔案內容的 "foo" 取代成 "bar"
$ find ./ -name "*.cpp" -print0 | xargs -0 sed -i 's/foo/bar/g'

find 根據檔案大小來找

這邊示範根據檔案大小來找,尤其是在硬碟空間不足時要清理垃圾時特別容易使用到XD
找出單檔大於500M的檔案,

1
$ find ./ -size +500M

找出單檔大於2G的檔案,

1
2
3
$ find ./ -size +2G
or
$ find ./ -size +2048M

找出單檔小於160k的照片並且刪除,

1
$ find ./ -name "*.jpg" -size -160k -delete

找出單檔大於160k的照片並且刪除.

1
$ find ./ -name "*.jpg" -size +160k -delete

這邊整理一下其他單位使用,
c:for bytes
k:for Kilobytes (units of 1024 bytes)
M:for Megabytes (units of 1048576 bytes)
G:for Gigabytes (units of 1073741824 bytes)

find 根據檔案修改日期來找

用 find 指令尋找當前目錄下在 5 分鐘以內變動過的所有檔案,

1
find ./ -type f -mmin -5

用 find 指令尋找當前目錄下超過 5 分鐘以上變動過的所有檔案,同時也是 5 分鐘內沒有變動過的所有檔案,

1
find ./ -type f -mmin +5

用 find 指令尋找當前目錄下最後 5 分鐘到 10 分鐘內變動過的所有檔案,

1
find ./ -type f -mmin +5 -mmin -10

用 find 指令尋找當前目錄下 3 天內變動過的所有檔案,

1
find ./ -type f -mtime -3

這邊整理一下這幾個選項的差異,
amin:檔案的最後存取時間(last access),時間單位為分鐘
atime:檔案的最後存取時間(last access),時間單位為天
cmin:檔案的狀態資訊最後修改的時間(last change),時間單位為分鐘
ctime:檔案的狀態資訊最後修改的時間(last change),時間單位為天
mmin:檔案的最後修改時間(last modify),時間單位為分鐘
mtime:檔案的最後修改時間(last modify),時間單位為天

find 排除目錄或排除多個目錄

這邊示範 find 排除目錄的語法,先介紹排除單一目錄,假如 find txt 文字檔時要排除當前 test 目錄,指令如下,

1
find ./ -name "*.txt" -path "./test" -prune -o -print

排除多個目錄的話,例如排除當前 test 與 tools 目錄,指令如下,

1
find ./ -name "*.txt" -path "./test" -prune -o -path "./tools" -prune -o -print

排除 2 個以上目錄的指令就依此類推

find 計算找到的檔案數量

如果想要計算 find 找到檔案結果的數量的話可以配合 wc 這個指令,這邊示範找當前目錄下副檔名為 jpg 的檔案數量,

1
$ find ./ -name "*.jpg" | wc -l

其它相關文章推薦
Linux 常用指令教學懶人包
Linux cut 字串處理用法與範例
Linux sed 字串取代用法與範例
Linux grep/ack/ag 搜尋字串用法與範例
Linux du 查詢硬碟剩餘空間/資料夾容量用法與範例
Linux wget 下載檔案用法與範例

C++ std::sort 排序用法與範例完整介紹

本篇介紹 C++ 的 std::sort 排序用法,C++ 最常用到的就是對 vector sort 排序,或對傳統陣列 array sort 排序,以上兩種都會在本篇介紹,C++ 的 sort 預設排序方式是升序,也可以用降序或自定義的排序方式,詳見下列內容,接下來就開始介紹 C++ 的 sort 排序吧。

市面上的排序法有很多,有泡沫排序法、快速排序法、合併排序法,這篇將介紹不用自己寫(刻),用 C++ STL 標準函式庫提供的 sort 來排序,
首先來簡單介紹 C++ sort 最基本的用法,
需要引入的標頭檔<algorithm>

1
2
3
vector<int> v;
// ...
std::sort(v.begin(), v.end());

這樣就可以完成排序囉~
接下來就認真介紹 C++ sort 多種範例與寫法,以及補充說明,分別如下,
範例1. 排序 sort array 傳統陣列,並使用預設排序方式(升序)
範例2. 排序 sort array 傳統陣列(降序)
範例3. 排序 sort vector,並使用預設排序方式(升序)
範例4. 排序 sort vector,並使用函式排序(降序)
範例5. 排序 sort vector,並使用 lambda function 匿名函式排序(升序)
範例6. 排序 sort vector,並使用函式物件排序(降序)
範例7. 排序字串 sort string (升序)
範例8. 排序自定義結構 sort struct (降序)
補充. 排序陣列的某部份
補充. 自動計算陣列總長度
補充. C++ 的 std::sort 跟 C 的 qsort 誰比較快?
補充. 誰也使用了 std::sort ?

範例1. 排序 sort array 傳統陣列,並使用預設排序方式(升序)

如何用 C++ sort array 對 c-style 傳統陣列排序的範例如下,
宣告一個 arr 傳統陣列並且初始化好數值,
再使用 std::sort 針對 array 進行排序,
預設提供的一個比較函數(升冪/升序/由小到大),
當你需要按照某種特定方式進行排序時,例如降冪,你需要給 sort 指定比較函數,後面例子會提到。

std-sort1.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// g++ std-sort1.cpp -o a.out
#include <iostream>
#include <algorithm>

int main() {
int arr[] = {4, 5, 8, 3, 7, 1, 2, 6, 10, 9};
std::sort(arr, arr+10);
std::cout << "sort array by default (increasing):" << std::endl;
for (int i = 0; i < 10; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;

return 0;
}

輸出如下:

1
2
sort array by default (increasing):
1 2 3 4 5 6 7 8 9 10

你也可以用 std::begin()std::end() 取得 c-style 陣列的頭跟尾,像這樣寫

1
std::sort(std::begin(arr), std::end(arr));

範例2. 排序 sort array 傳統陣列(降序)

從上例可了解 c++ sort 升冪的使用,那降冪(降序/由大到小)要如何使用呢?
分為兩種方法,一種是自訂排序方式,在 std::sort 第三個參數傳入一個可以降冪的 function object 函式物件(又稱 functor 仿函式),這個方式稍後的範例會提到,
另一種方法是使用<functional>提供的比較函式物件讓我們方便使用,在 std::sort 第三個參數傳入比較樣板函式物件即可,
<functional>提供的比較樣板函式物件分別有
less<Type>:小於,i+1小於i的就交換swap(升序)
less_equal<Type>:小於等於,i+1小於等於i的就交換swap(升序)
greater<Type>:大於,i+1大於i的就交換swap(降序)
greater_equal<Type>:大於等於,i+1大於等於i的就交換swap(降序)
equal_to<Type>:等於,i+1等於i的就交換swap
not_equal_to<Type>:不等於,i+1不等於i的就交換swap
以上這幾種,而實際上用法如下:

升序:std::sort(begin, end, less<Type>());
降序:std::sort(begin, end, greater<Type>());

接著來看看實際範例吧!
在這個例子中,我們就是在 std::sort 第三個參數傳入 std::greater<int>() 來達成降冪(降序/由大到小)的排序,

std-sort2.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// g++ std-sort2.cpp -o a.out
#include <iostream>
#include <algorithm>
#include <functional>

int main() {
int arr[] = {4, 5, 8, 3, 7, 1, 2, 6, 10, 9};
std::sort(arr, arr+10, std::greater<int>());
std::cout << "sort array (decreasing):" << std::endl;
for (int i = 0; i < 10; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;

return 0;
}

輸出如下:

1
2
sort array (decreasing):
10 9 8 7 6 5 4 3 2 1

範例3. 排序 sort vector,並使用預設排序方式(升序)

上述例子都是介紹傳統陣列,接著開始介紹對 std::vector 容器排序的用法,將 vector 帶入 std::sort 函式排序,
你可以使用 std::sort(v.begin(), v.begin()+10); 的方式,
也可以使用 std::sort(v.begin(), v.end()); 的方式隨君喜好,
最後的結果都是升序的(由小到大),如下範例所示,
關於怎麼在 std::sort 函式的第三個參數帶入自訂的函式將會下個範例會介紹,以及簡單地理解第三個參數怎麼在 std::sort 裡運作,

std-sort3.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// g++ std-sort3.cpp -o a.out -std=c++11
#include <iostream>
#include <algorithm>
#include <vector>

int main() {
std::vector<int> v = {5, 4, 1, 7, 3, 8, 9, 10, 6, 2};
std::sort(v.begin(), v.begin()+10);
// or
//std::sort(v.begin(), v.end());
std::cout << "sort vecotr by default (increasing):" << std::endl;
for (int i = 0; i < v.size(); i++) {
std::cout << v[i] << " ";
}
std::cout << std::endl;

return 0;
}

輸出如下:

1
2
sort vecotr by default (increasing):
1 2 3 4 5 6 7 8 9 10

範例4. 排序 sort vector,並使用函式排序(降序)

上述提到 std::sort 可以使用自訂排序方式來排序, 這個例子就來示範一下,
如下列範例所示,使用 mycompare function 作為比較函式,我們可以在 mycompare 裡自訂我們想要的排序規則,
例子中的 return a > b 為降序的示範,這其實跟上述介紹的 greater<Type> 是等價一樣的效果,

簡單理解第三個參數怎麼在 std::sort 內部裡運作
先不討論 std::sort 內部是用哪種排序演算法,
我們可以先簡單地解讀成 sort 內部有兩層迴圈,不斷地針對 vector 的 index 0 頭掃到 index n 尾,直到排序結束為止,
而迴圈每次都會將 i+1 與 i 帶入 mycompare 的參數,i+1 就是 a, i 就是 b,
如此一來 a > b 就會將 i+1 與 i 交換 swap,這樣就會一直將越大的數值排越前面,
若不懂排序原理的可以參考最簡單的排序法:泡沫排序法,

std-sort4.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// g++ std-sort4.cpp -o a.out -std=c++11
#include <iostream>
#include <algorithm>
#include <vector>

bool mycompare(int a, int b) {
return a > b; // 降序排列
}

int main() {
int arr[] = {5, 4, 1, 7, 3, 8, 9, 10, 6, 2};
std::vector<int> v(arr, arr+10);
std::sort(v.begin(), v.end(), mycompare);
std::cout << "sort vecotr by function (decreasing):" << std::endl;
for (int i : v) {
std::cout << i << " ";
}
std::cout << std::endl;

return 0;
}

輸出如下:

1
2
sort vecotr by function (decreasing):
10 9 8 7 6 5 4 3 2 1

另外降序還有第三個方式,就是使用 vector 反向迭代的方式,從 vector 尾部逆向迭代到頭部,這算是善用 vector 的小技巧,例如下面這樣寫,

1
std::sort(v.rbegin(), v.rend());

範例5. 排序 sort vector,並使用 lambda function 匿名函式排序(升序)

這個範例介紹匿名函式 lambda function 來搭配 sort 使用,
如下列範例所示,在sort第三個參數中直接帶入一個匿名函式 lambda function 作為參數,
其匿名函式內容為回傳 a < b,這樣就達成的升序的排列結果。

std-sort5.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// g++ std-sort5.cpp -o a.out -std=c++11
#include <iostream>
#include <algorithm>
#include <vector>

int main() {
std::vector<int> v = {5, 4, 1, 7, 3, 8, 9, 10, 6, 2};
std::sort(v.begin(), v.end(), [](int a, int b){
return a < b; // 升序排列
});
std::cout << "sort vecotr by lambda function (increasing):" << std::endl;
for (int i : v) {
std::cout << i << " ";
}
std::cout << std::endl;

return 0;
}

輸出如下:

1
2
sort vecotr by lambda function (increasing):
1 2 3 4 5 6 7 8 9 10

範例6. 排序 sort vector,並使用函式物件排序(降序)

sort 帶入 function object 函式物件來排序,function object 函式物件又稱 functor 仿函式,
其實就是重載了 operator() 運算子的一個類別,如下列範例中的 myclass,
其目的在於讓它可以像函式一樣呼叫使用,例如:myclass(5, 3); 會回傳 5,
那我們就來看看怎麼用函式物件結合 sort 來使用,
如下範例所示,直接在 std::sort 的第三個參數帶入 myclass 函式物件,

std-sort6.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// g++ std-sort6.cpp -o a.out -std=c++11
#include <iostream>
#include <algorithm>
#include <vector>

struct myclass {
bool operator() (int a, int b) { return a > b; } // 降序排列
} myobject;

int main() {
std::vector<int> v = {5, 4, 1, 7, 3, 8, 9, 10, 6, 2};
std::sort(v.begin(), v.end(), myobject);
std::cout << "sort vecotr by object (decreasing):" << std::endl;
for (int i : v) {
std::cout << i << " ";
}
std::cout << std::endl;

return 0;
}

輸出如下:

1
2
sort vecotr by object (decreasing):
10 9 8 7 6 5 4 3 2 1

範例7. 排序字串 sort string (升序)

介紹一下在 C++ 中怎麼對字串 std::string 排序 sort,很簡單,就直接看範例吧!
將 std::string 傳入 std::sort 函式,在第三參數帶入 std::less<char>() 來達成升冪(升序/由小到大)的排序(按ASCII碼字母順序),

std-sort7.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// g++ std-sort7.cpp -o a.out -std=c++11
#include <iostream>
#include <algorithm>
#include <string>

int main() {
std::string s = "eacdb";
std::sort(s.begin(), s.end(), std::less<char>());
std::cout << "sort string (increasing):" << std::endl;
for (char c : s) {
std::cout << c << " ";
}
std::cout << std::endl;

return 0;
}

輸出如下:

1
2
sort string (increasing):
a b c d e

範例8. 排序自定義結構 sort struct (降序)

這邊介紹 C++ 如何排序自定義結構 sort struct ,下面範例中有個 student 的結構,內部欄位有姓名與分數,
我們最常對學生的分數按高低分來排名,一開始先初始化學生的姓名與分數後,接著讓 mycompare 回傳降序的方式,
而這個 mycompare 是接受我們自定義的 student 結構,並且最後回傳 student.score 的比較結果,
降序的方式上述已經介紹過,忘了請往回看,
之後就可以按照分數由高到低排序完成。

std-sort8.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// g++ std-sort8.cpp -o a.out -std=c++11
#include <iostream>
#include <algorithm>
#include <string>

struct student {
std::string name;
int score;
};

bool mycompare(student s1, student s2){
return s1.score > s2.score;
}

int main() {
student st[4];
st[0].name = "bob";
st[0].score = 70;
st[1].name = "cindy";
st[1].score = 66;
st[2].name = "alice";
st[2].score = 77;
st[3].name = "alice";
st[3].score = 76;

std::sort(st, st+4, mycompare);
std::cout << "sort struct (decreasing):" << std::endl;
for (student s : st) {
std::cout << s.name << " " << s.score << std::endl;
}
std::cout << std::endl;

return 0;
}

這邊故意設計兩個學生都叫 alice 但分數不一樣的情形,看看是不是也能排序,結果也是可以的,
輸出如下:

1
2
3
4
5
sort struct (decreasing):
alice 77
alice 76
bob 70
cindy 66

好了!以上就是 C++ std::sort 的介紹與用法範例,sort 的排序法有很多種,
知名的排序法有泡沫排序法、快速排序法、合併排序法,
排序不但是實務上常用的技能到且考試很容易考得考題,可見排序的重要性,所以一定要好好地學起來。

補充. 排序陣列的某部份

如果你只想排序前5筆數值,其他數值不排序,可以這樣寫,

1
2
int arr[] = {4, 5, 8, 3, 7, 1, 2, 6, 10, 9};
std::sort(arr, arr+5);

輸出如下:

1
3 4 5 7 8 1 2 6 10 9

如果是用 vector 的話,就變成這樣寫,

1
2
std::vector<int> arr = {4, 5, 8, 3, 7, 1, 2, 6, 10, 9};
std::sort(arr.begin(), arr.begin()+5);

輸出結果也是一樣的。

如果你想排序第3筆到第5筆數值,其他數值不動,可以這樣寫,
(排序從std::sort第一個參數開始,直到(小於)第二個參數之前)

1
2
int arr[] = {4, 5, 8, 3, 7, 1, 2, 6, 10, 9};
std::sort(arr+2, arr+5);

輸出如下:

1
4 5 3 7 8 1 2 6 10 9

如果是用 vector 的話,就變成這樣寫,

1
2
std::vector<int> arr = {4, 5, 8, 3, 7, 1, 2, 6, 10, 9};
std::sort(arr.begin()+2, arr.begin()+5);

輸出結果也是一樣的。
其他依此類推。

補充. 自動計算陣列總長度

每次排序整個 c-style 陣列需要先計算好陣列的總長度,如果你想寫得聰明一點,避免自己手動去算有幾個或算錯的話,
可以這樣寫,用 sizeof 來取得整個陣列的總 byte 數除以 int 的 byte 數,就是整個陣列的總長度了,

1
2
3
int arr[] = {4, 5, 8, 3, 7, 1, 2, 6, 10, 9};
int len = sizeof(arr) / sizeof(int);
std::sort(arr, arr + len);

補充. C++ 的 std::sort 跟 C 的 qsort 誰比較快?

C++ STL 的 std::sort (<algorithm>) 與 C 的 qsort (<cstdlib>)這兩個排序哪個比較快?
這問題顯然大家已經知道了,那就是 C++ STL 的 std::sort 比 qsort 更快,
以前已經知道有個快速排序法已經很快了,沒想到 std::sort 還更快,
不論你想手刻或是使用 qsort 一定都不會比 std::sort 還要快,對這部份有興趣的也可自行作實驗挑戰看看
如果沒把握還是乖乖用大神寫好的 std::sort 吧!

補充. 誰也使用了 std::sort ?

誰也使用了 std::sort ? 來看看別人是怎麼使用的吧!
OpenCV 內部使用 std::sort 的範例,OpenCV VideoBackend 有很多種,
所以這邊是根據 VideoBackend 的 Priority 來進行排序,以便之後的使用
https://github.com/opencv/opencv/blob/master/modules/videoio/src/videoio_registry.cpp

1
2
3
4
5
6
bool sortByPriority(const VideoBackendInfo &lhs, const VideoBackendInfo &rhs)
{
return lhs.priority > rhs.priority;
}

std::sort(enabledBackends.begin(), enabledBackends.end(), sortByPriority);

以上就是 C++ std::sort 排序的用法與範例介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

其他參考
sort - C++ Reference
http://www.cplusplus.com/reference/algorithm/sort/
詳細解說 STL 排序(Sort) - C++ Programmer’s Cookbook - C++博客
http://www.cppblog.com/mzty/archive/2005/12/15/1770.aspx

其它相關文章推薦
C/C++ 新手入門教學懶人包
std::thread 用法與範例
std::deque 用法與範例
std::find 用法與範例
std::mutex 用法與範例
std::unordered_map 用法與範例
std::random_shuffle 產生不重複的隨機亂數
std::shared_ptr 用法與範例
std::async 用法與範例

Python OpenCV 顯示camera攝影機串流影像

本篇介紹如何用 Python 搭配 OpenCV 模組的 cv2.VideoCapture 開啟攝影機並顯示攝影機串流的畫面。

使用範例

如果遇到 ImportError: No module named 'cv2' 這個錯誤訊息的話,請安裝 python 的 OpenCV 模組,參考這篇安裝吧!。

要擷取攝影機影像,需要先建立一個 VideoCapture,可以參考下列範例中的 cv2.VideoCapture(0)
cv2.VideoCapture 的參數代表攝影機裝置的代號(device index),如果有多台攝影機的話就可以從攝影機裝置的代號來指定,
但通常只有一台攝影機,所以這邊攝影機代號代號0,
之後使用 cap.isOpened() 來確認攝影機裝置有沒有開啟,之後在迴圈使用 cap.read() 每次從攝影機讀取一張影像,
來作進一步的影像處理,這邊的例子簡單地使用 cv2.cvtColor() 的將影像從彩色轉成灰階,最後使用 cv2.imshow() 將影像顯示出來,
並且在迴圈內使用 cv2.waitKey(1) 等待按鍵事件發生,如果按下 q 鍵的話則 break 離開這個迴圈。
最後別忘了要使用 release() 來釋放該攝影機裝置。

opencv-camera.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import cv2

cap = cv2.VideoCapture(0)
if not cap.isOpened():
print("Cannot open camera")
exit()

#cv2.namedWindow("live", cv2.WINDOW_AUTOSIZE); # 命名一個視窗,可不寫
while(True):
# 擷取影像
ret, frame = cap.read()
if not ret:
print("Can't receive frame (stream end?). Exiting ...")
break

# 彩色轉灰階
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

# 顯示圖片
cv2.imshow('live', frame)
#cv2.imshow('live', gray)

# 按下 q 鍵離開迴圈
if cv2.waitKey(1) == ord('q'):
break

# 釋放該攝影機裝置
cap.release()
cv2.destroyAllWindows()

下一篇教學是如何播放video影片檔案

其他參考
OpenCV: Getting Started with Videos
https://docs.opencv.org/master/dd/d43/tutorial_py_video_display.html
OpenCV 擷取網路攝影機串流影像,處理並寫入影片檔案教學 - G. T. Wang
https://blog.gtwang.org/programming/opencv-webcam-video-capture-and-file-write-tutorial/

其它相關文章推薦
C++ OpenCV 顯示camera攝影機串流影像
Python OpenCV 彩色轉灰階(RGB/BGR to GRAY)
Python OpenCV 彩色轉HSV(RGB/BGR to HSV)
Python OpenCV 彩色轉YCbCr(RGB/BGR to YCbCr)
Python OpenCV 灰階轉彩色(Gray to RGB/BGR)
Python OpenCV 影像二值化 Image Thresholding
Python OpenCV 影像平滑模糊化 blur
Python OpenCV 影像邊緣偵測 Canny Edge Detection
Python OpenCV 垂直vconcat 和水平hconcat 影像拼接
Python OpenCV resize 圖片縮放
Python 新手入門教學懶人包
小專案 Python OpenCV 圖片轉字元圖畫