跳至主要内容

[指令] Command Line 操作

keywords: zsh, iterm2, terminal, linux, unix
open -a [appToOpen] [fileName]       # 指定特定應用程式開啟檔案
tail -f [filename] # 從結尾開始讀檔,並且持續監控變化
history | grep [pattern] # 找出最近在 terminal 中輸入的內容

Linux Terminal Command Reference @ linux community

變數

$PATH

所有能夠在 Terminal 中直接被執行的程式,都需要放在 $PATH 內,否則需要先指定到該程式所在的資料夾後才能執行。

echo $PATH

如果想要將其他路徑添加到 $PATH 中,可以在 .bash_profile.zshrc 中使用:

  • PATH="" 後面使用 $PATH 是為了把原本已有的 PATH 保留下來,否則會覆蓋掉。
  • : 當作不同路徑的區隔
# .zshrc
export PATH="$PATH:~/my-scrips"

$SHELL / $0

當前 Terminal 使用的 shell:

echo $SHELL   # /opt/homebrew/bin/zsh
echo $0 # -zsh

$USER / $USERNAME

使用者名稱:

echo $USER    # pjchender
echo $USERNAME # pjchender

操作資料夾

cd <directory>                         # 切換目錄

pwd # 取得目前所在的位置

ls -alpt # 列出目前的檔案列表,搭配 -a(顯示隱藏檔), -l(顯示完整資訊),
# -p(顯示較清楚),-t(根據最後修改時間排序檔案)
ls | wc -l # 顯示檔案數量

mkdir <directory> # 建立新的目錄
mkdir -p ~/Library/a_folder # 即使 Library 資料夾不存在也直接建立到 a_folder

touch # 建立檔案

cp <file> <directory> # 複製檔案
cp index.html about.html # 把檔案 index.html 複製一份成 about.html
cp -r a_folder b_bolder # 把 a_folder 資料夾複製進去 b_folder 資料夾中

mv <file-old> <file-new|directory> # 移動檔案
mv index.html info.html # 把檔案 index.html 更名成 info.html(重新命名、改名)
mv index.html a_folder/ # 把 index.html 移動到 a_folder 資料夾中

rm <file|directory> # 刪除檔案
rm *.html # 刪除目錄中的所有 html
rm -r <folder> # 刪除整個 a_folder
rm -f <file> # 刪除檔案
rm -rf [folder_name] # 刪除資料夾(不論裡面有沒有內容或其他資料夾)

sudo # 暫時取得權限

改變資料與檔案權限 chmod

$ chmod 644 *     # 檔案用 644
$ chmod 755 * # 目錄用 755

# 一次修改因為檔案權限修改後,使得 git 認為檔案不同的情況
$ git ls-files -m | xargs chmod 644

# 一次修改所有資料夾底下的檔案或檔案
$ find YOUR_PATH -type d | xargs chmod 755
$ find YOUR_PATH -type f | xargs chmod 644
$ sudo find /path/to/dir -type f -exec chmod 0664 {} \; # 如果上一行出現錯誤,可試試這行

補充:

# 文件的權限類型

rwx = 111 = 7
rw- = 110 = 6
r-x = 101 = 5
r-- = 100 = 4
-wx = 011 = 3
-w- = 010 = 2
--x = 001 = 1
--- = 000 = 0

# 文件所屬群組

<擁有者> - <群組> - <其它>

Linux 權限詳解

讀取檔案

cat <file>                       # 將檔案內容輸出在終端機上

less <file> # 使用分頁的方式顯示內容

head <file> # 只顯示檔案的前 10 行

man open # 查看 open 指令說明

open # 開啟檔案
open -a [appToOpen] [fileName] # 指定特定應用程式開啟檔案

寫入檔案


# <cmd> > <file> # 將 <cmd> 的內容寫入 <file>
echo "my content" > my_file.rb # 取代原本的檔案內容

# <cmd> >> <file> # 將 <cmd> 的內容添加到 <file>
echo "my content" >> my_file.rb # 添加到原本的檔案內容

找檔案

# 尋找空檔案
$ find . -empty

# 尋找名稱後綴為 .del 的資料夾
# -iname 表示 case insensitive
# -type 表示要資料夾(directory)或檔案(file)
$ find . -iname '*.del' -type d

# 尋找名稱後綴為 .del 的檔案
$ find . -iname '*.del' -type f

# 尋找名稱副檔名為 .tsx 的檔案,但不要找 node_modules 裡的
$ find . -type f -name '*.tsx' ! -path "./node_modules/*"

grep:找檔案中的內容

$ grep -ir '<content-to-find>' '<folder-path>'
# i: case-insensitive
# r: recursive
# l: 只顯示資料夾

$ grep -irl 'foobar' . # 從當前目錄開始找

搜尋某一檔案中的內容:

# 找出 .env 中以 "#" 開頭資料
$ grep '^#' .env

# 找出 .env 中不是以 "#" 開頭的資料
$ grep -v '^#' .env

剪貼簿(clipboard)

keywords: pbcopy, pbpaste

Copy to Clipboard from Command Line @ DWB

# 複製
$ git diff | pbcopy # 將 stdout 的結果複製到剪貼簿
$ pbcopy < tsconfig.json # 將檔案內容複製到剪貼簿

# 貼上
$ pbpaste > foo.json # 將剪貼簿的內容貼到檔案內

glob

pattern matching

如果好奇該 glob 會匹配到的檔案有哪些,可以使用 echo 查看,例如:

echo (styles|constants)/**/dist  # 在 styles 或 constants 資料夾內的 dist
echo packages/*

asterisk

What is the ** glob character? @ StackOverflow

使用單一個星號(single asterisk),可以用來匹配一層內的資料夾,例如 /x/*/y 可以匹配到:

/x/a/y
/x/b/y

但如果是使用雙個星號(double asterisk),則會以 recursive 的方式匹配,例如 /x/**/y 可以匹配到:

/x/any/number/of/levels/y

同時,如果不只是資料夾,還要針對特定副檔名時,可以這樣寫:

const path = [
'*.js', // 該層內所有副檔名為 .js 檔案(不管檔名)
'index.*', // 該層內所有檔名為 index 的檔案(不管副檔名)

'packages/*', // packages 下一層內的所有檔案
'packages/*/*.json', // packages/b/package.json
// packages/a/package.json

'packages/**/*.json', // packages/a/node_modules/lodash/package.json
// packages/a/node_modules/the-answer/package.json
// packages/a/package-lock.json
// packages/a/package.json
// packages/b/package.json
'packages/**/*', // packages 內的所有檔案

'src/index.js', // 一定要是 src/index.js
'src/js/*.js', // 匹配到 src/js/ 裡的所有 .js
'src/js/**/*.js', // 匹配到 src/js/any/number/of/level/ 裡的所有 .js
];

cron:設定 cron jobs

參考網站

語法

設定格式
*    *    *    *    *  sh /path/to/script.sh
- - - - -
| | | | |
| | | | +----- day of week(0-6 | Sun-Sat)
| | | +---------- month (1-12)
| | +--------------- day of month (1-31)
| +-------------------- hour (0-23)
+------------------------- minute (0-59)
格式範例
m h d m d
5 2 * * * # 每天的 02:05:00 會執行(At 02:05)
5 * * * * # 每天每小時的 **:05:00 會執行(At minute 5)

# range of value
5 0-2 * * * # 每天的 00:05:00, 01:05:00, 02:05:00 會執行

# value list separator
0 0 1,15 * * # 每個月的 1 號和 5 號的 00:00:00 會執行(At 00:00 on day-of-month 1 and 15.)

# step values
0 */2 * * * # 每兩小時的 **:00:00 會執行
*/15 * * * * # 每 15 分鐘會執行

# combination
0 4-8/2 * * * # 每天 04:00:00~08:00:00 的每兩小時會執行

設定 cron jobs

# 進入設定檔
EDITOR=vim crontab -e

假設我們寫了一個 Shell Script,檔名是 ~/test.sh

test.sh
#! /bin/bash

echo "The time is $(date)"

使用 crontab 進入設定檔後,即可寫入:

* * * * * sh /path/to/test.sh

# Mac 上如果希望紀錄 log
# * * * * * sh /path/to/test.sh >>/tmp/cron.log 2>&1

# Unix 會自動紀錄,但可以將輸出的內容加上文字方便辨識
# * * * * * sh /path/to/test.sh 2>&1 | logger -t test.sh

檢視 cron jobs 的 log

Unix

# Unix
$ sudo cat /var/log/syslog

# 也可以使用 tail 檢視
$ sudo tail -f /var/log/syslog

Mac

如果是 Mac 的話,預設是沒有紀錄的,需要的話,可以在設定 crontab 的最後加上輸出的檔案,例如:

# 將 output 和 error 分別輸出在不同檔案,且每次覆蓋
5 * * * * sh /path/to/script.sh >/tmp/stdout.log 2>/tmp/stderr.log

# 檢視 log
# sudo cat /tmp/stdout.log
# sudo cat /tmp/stderr.log
# 將 output 和 error 輸出在同一隻檔案,且不斷添加
5 * * * * sh /path/to/script.sh >>/tmp/cron.log 2>&1

# 檢視 log
# sudo cat /tmp/cron.log

其他指令

clear                     # 清除畫面
history # 檢視 terminal 紀錄
history | grep migration # 搜尋含有 "migration" 的紀錄

輸入多行的 command 時,在每一行的最後使用 \ 或 `,最後一行則不要輸。

=> bin/rails generate scaffold Product \
title: string description:text image_url:string price:decimal

使用 && 可以一次執行兩個指令:

cd ~/Projects/Backup && node index.js

讓程序在背景執行

keywords: background, bg, fg
$ scrpcy    # 執行某個程序
CTRL + Z # 按下
$ bg # 進入背景
$ jobs # 列出所有在背景的程序
$ fg n # 將第 n 個程序移回前景執行(n 可以用 jobs 看)

run process in background

重新載入設定檔

keywords: source
source ~/.zshrc

看目前的 port 被誰佔住

# 查看 port 被誰佔住
lsof -wni tcp:3000
lsof -i tcp:3000

# 把那個 port 刪掉
kill -9 <pid>

列出系統執行的程序

ps aux      # show me all running process
ps aux | grep ssh # 列出正在執行 ssh 的程序

killall node # 刪除所有正在執行的 node 程序

複製內容到剪貼簿

keywords: pasteboard, pbcopy
# paste board
git diff | pbcopy. # 將 git diff 的結果複製到剪貼簿

# pbpaste 可以貼上

壓縮與解壓縮檔案(zip and unzip)

壓縮

# zip -r <output-file> <input-file>
$ zip -r function.zip . # 將該資料夾內的所有檔案壓縮為 function.zip
$ zip -9 -r --exclude="*.git*" function.zip . # 忽略掉 .git 資料夾內的檔案
$ zip -9 -r function.zip . -x '*.git*' '*.vscode*' # 忽略掉兩個以上的檔案

解壓縮

unzip -a file.zip -d /home/phpini

壓縮與打包檔案(tar)

# 查看 tar 指令
$ man tar

# 建立壓縮檔
# -z:使用 gzip 壓縮檔案
# -c:create
# -f:location of archived
# -P:--absolute-paths
tar -zcf node_modules.tar.gz -P /app/node_modules/

# 解壓縮
tar -xf node_modules.tar.gz

印出資料夾結構(print file tree

參考資料

Tree @ Manned

online tool

如果是要畫出資料夾結構的話,除了使用 Tree 這個 CLI 工具外,也可以使用網路上的這個工具

安裝:

$ brew install tree

使用:

# -a                           印出所有檔案(包含隱藏檔)
# -d 只印出資料夾
# -L 2 只深入到第二層
# -C 顯示顏色
# -P node_modules 只列出符合 node_modules pattern 的檔案
# -I node_modules 忽略掉符合 node_modules pattern 的檔案
# -f 列出檔案的 full path
# --gitignore 使用 .gitignore
# --ignore-case 忽略大小寫
# --prune 不要顯示沒有對應檔案的資料夾

# 列出兩層的資料夾
$ tree . -d -L 2 -I node_modules --gitignore

# 列出有 *.tsx 在內的資料夾(搭配 -d 會有點故障)
$ tree -P "*.tsx" . --gitignore --prune

將 .env 中的所有環境變數載入 Shell 中

export $(grep -v '^#' .env | xargs)

接著可以用 echo $SOME_ENV_VARIABLE 來檢查看看是否有載入成功

切換 MacOS 中預設的 Shell

修改 Terminal 預設的 Shell

在 MacOS 中預設的 Shell 是 bash,打開 MacOS 的 Terminal,進入「偏好設定」--> 「一般」--> 「打開 shell 的設定」:

  • zsh: /usr/local/bin/zsh
  • bash: /bin/bash

imgur

修改使用者預設的 Shell

如果要使用指令更改預設的 shell 可以使用 chsh 指令,這種方式會同時改變使用 iTerm 等登入時預設的 Shell:

chsh -s /bin/zsh

Change Shell MacOS X

常見問題

使用 source 和直接執行某個 script 檔的差別

What is the difference between executing a Bash script vs sourcing it? @ StackOverflow

如同 stack overflow 上的解答所說,不論是使用 source 或直接執行(execute)某個 script 檔,這些檔案中的內容都會被逐行執行,差別在於檔案中所定義的變數是否會暴露到當前的 shell 中:

  • 使用 source 的話,檔案中所定義的變數或函式都會進到當前的 shell 中,即使檔案執行結束後,還是可以在 shell 中呼叫到該函式或檔案
  • 使用 execute 的話,一旦檔案執行結束後,該檔案中所定義的變數和函式都會消失,無法在該 shell 中再次被使用
# execute 的方式
./myscript

# source 的方式
source myscript
. myscript # 在 bash 中將 source 視為 . 的 alias

舉例來說:

# foo.sh
NAME="Aaron"
echo $NAME

如果是使用 execute 的方式,程式執行完後,$NAME 這個變數就不存在該 shell 中:

./foo.sh

echo $NAME # 沒有東西

但如果是使用 source 的話,程式執行完後,$NAME 這個變數依然存在該 shell 中:

source foo.sh

echo $NAME # Aaron

參考資料