Nushell 使用說明及總結
介紹
- 來自 UNIX Shell 的管道, 把多個命令連接在一起
- 函數式編程風格
- 豐富的對象化的數據結構
- 對結構化文件的處理, 比如 JSON, XML, CSV, TOML 等
- 基于模板的字符串解析
- 在線幫助
- 輸出結果是彩色的, 而且是表格化的, 還自帶編號 ! 真貼心 !
- Footprint 很小, 安裝后 < 30M (0.85 版本)
基礎知識
1. 數據類型
- 可以用 describe 命令來獲得前一個管道輸出的數據類型描述
- 可以用 into <type> 來把前一個管道輸出的數據類型轉換成當前引用的數據類型
- 字符串可以用單引號, 也可以用雙引號, 兩者稍有不同
- 字符串也可以不用任何引號, 這叫裸字符串. 比引號更好的地方在于, 裸串不需要轉義
open d:\tmp\aa.txt
open "d:\\tmp\\aa.txt"
- null 是特殊的,內定的數據, 表示 "沒數據/未定義", 類似 C 中的 void, C# 中的 null, 或 Python 中的 None.
[[meal size]; [arepa, null] ] | is-empty meal # false
- 區間還可以用負數表示, 但是需要加上括號, 否則容易引起編譯器誤解. 例如下面的語句:
'abcd12345678' | str substring (-5..-2) #456
- 用字符串的 parse 命令, 可以把一個字符串解釋成若干列. 這個 "按模式切片" 的功能, 真是日志分析的利器.
2. 變量
- Nu 中的變量,其實是 "常量", 一旦賦值后就不允許修改
- 與常規編程語言類似, 變量有作用域, 子域可以使用同名變量, 不會覆蓋父域的同名變量
- 變量支持路徑.
print $val.name
3. 子表達式
- 可以通過圓括號 () 來執行一個子表達式并使用其結果.
(ls)
- 子表達式也支持路徑
(ls).name
- 子表達式可以簡化
ls | where size > 10kb
ls | where {|it| $it.size > 10k}
ls | where $it.size > 10k
或
ls | where ($it.size > 10k)
- 簡化后的子表達式, 路徑名必須寫在前面.
ls | where 10k < size
但以下語句合法:
ls | where 10k < $it.size
ls | where (10k < $it.size)
ls 命令介紹
完整格式
ls {flags} (pattern)
Flags:
--long, -l : 長格式, 顯示所有的列 (稍慢, 且列內容依賴于平臺)
--short-names, -s : 僅顯示文件名, 不顯示路徑
--full-paths, -f : 把路徑顯示成絕對路徑
--du, -d : 在目錄尺寸列的位置, 顯示整個目錄所有文件和目錄占用的空間 (disk usage)
--directory, -D : 顯示指定的目錄, 而不是其內容
--mime-type, -m : 針對文件, 在類型列, 顯示其 mime 類型, 而不是 'file'.
只根據文件名決定其 mime 類型, 不檢查文件內容
參數:
輸入輸出:
輸出: table (表格)
ls | describe
table <
name: string, // 文件名/目錄名
type: string, // 類型 (file/dir)
size: filesize, // 文件大小
modified: date > // 最后一次修改日期+時間
ls -l | describe
table <
name: string, // 文件名/目錄名
type: string, // 類型 (file/dir)
target: nothing, // ??
readonly: bool, // 是否只讀
size: filesize, // 文件大小
created: date, // 創建日期+時間
accessed: date, // 訪問日期+時間
modified: date> // 最后一次修改日期+時間
例子:
列出指定類型的文件
ls *.txt
列出子目錄下的文件/目錄
ls out
列出文件及目錄, 但是其名稱中不包含 bar
ls -s | where name !~ bar
只列出目錄, 忽略文件類型
ls | where type == 'dir'
列出最近 7 天修改過的所有文件, 并且遞歸子目錄
ls **\*.* | where type == 'file' and modified > ((date now) - 7day)
把目標目錄放在變量中
let dir = "E:\\Work";
ls $"($dir)\\**\\*.cpp"
按大小排序, 倒序
ls | sort-by size | reverse
顯示部分列 - 文件名及大小 (忽略修改時間等)
ls | select name, size
把輸出重定向到一個文件
ls | get name | save a:\aa.txt
注意: 重定向也要通過管道操作符, 不能用傳統 shell 的 >.
列出特定條件的文件, 條件由我指定
ls | where ($it.name | str contains -i "txt")
表格 (table) 及處理
Nu 提供了許多處理表格的命令. 本章總結一下.
ls 命令的輸出就是一個表格, 因此本節的大多數示例都用 ls 命令生成表格
排序 - sort-by
可以用 sort-by 命令對一個表進行排序, 參數為列名.
例如
ls | sort-by size
選取 - select
可以從表中選擇特定的列或行來形成新的表格
選取 - 列. select 后面帶一個或多個列名
ls | select name size
選取 - 行:
first 數字: 指定選擇開始的 N 行
skip 數字: 跳過不需要的 N 行
select 數字: 選擇指定的一行
例子:
ls | sort-by size | first 5
ls | sort-by size | skip 2
從表格提取數據 - get
參數: 列名. 返回類型: 列表
例子:
ls | get name
上述 get 操作返回一個列表. 可以用如下語句確認輸出類型:
ls | get name | describe
返回如下:
list <string>
列表 (list) 及處理
什么是列表 - List
列表 (List) 是一個有序的值的集合.
可以用方括號創建一個列表, 元素之間的間隔可以是空格或逗號.
例如
[1 2 3]
[a, b, c]
迭代列表 - each
要遍歷一個列表中的元素, 可以用 each 命令與 Nu 代碼塊來指定對一個元素可以做什么操作.
塊參數 (例如 { |it| echo $it } 中的 |it|) 通常是當前的元素.
ls | get name | each {|it| echo $it}
可以用 where 命令來過濾, 得到列表的子集. 例如
let colors = [red orange yellow green blue purple]
$colors | where ($it | str ends-with 'e')
又如
let lst = [1, 3, 5, 7, 9]
$lst | where ($it > 5)
訪問單個元素
可以用 $lst.index 來獲得指定索引的單個元素. 其中 index 是給定的索引
例如
let lst = [1, 3, 5, 7, 9]
$lst.1 # 返回 3
如果索引在某個變量中, 可以使用 get 命令從列表中提前元素. 例如
let lst = [1, 3, 5, 7, 9]
let index = 2
$lst | get $index # 返回 5
當然, 下面的代碼也是可以的, 雖然有點啰嗦
let lst = [1, 3, 5, 7, 9]
$lst | get 2 # 返回 5
獲取元素個數 - length
length 命令可以獲得列表中的元素個數
let lst = [1, 3, 5, 7, 9]
$lst | length
當然, length 命令也可以用于獲得表格的行數
判空 - is-empty
is-empty 命令可以判定一個列表是否為空
例如:
let lst = [1, 3, 5, 7, 9]
$lst | is-empty # 返回 false
let lst = []
$lst | is-empty # 返回 true
又, is-empty 命令也可以用于字符串或表格
字符串及處理
字符串插值 (String interpolation) - 用對象名稱甚至表達式來直接替換原位
這是一種從原始文本和執行表達式的結果中構建文本的方法.
字符串插值將這些結果結合在一起, 返回新的字符串.
在單引號或雙引號前加入 $ 字符, 就表示字符串插值.
原位對象用 ($name) 表示, 原位表達式用 (exp) 表示
以下是幾個例子
let name = "Alice"
$"greeting, ($name)"
輸出:
greeting, Aliceing>
$"Do you know that 2+2 is (2 + 2) ?"
輸出:
Do you know that 2+2 is 4 ?
注意: 上述語句中的 (2 + 2) 中的兩個空格不可少, 否則就不是合法表達式
字符串分割 - split row
split row 命令從一個基于分隔符的字符串來創建一個列表.
例如:
let colors = "red orange yellow green blue purple"
$colors | split row ' '
返回:
│ 0 │ red │
│ 1 │ orange │
│ 2 │ yellow │
│ 3 │ green │
│ 4 │ blue │
│ 5 │ purple │
split column
從一個基于分隔符的字符串來創建一個表, 并為每個元素添加一列
例如:
let colors = "red orange yellow green blue purple"
$colors | split column ' '
返回:
│ # │ column1 │ column2 │ column3 │ column4 │ column5 │ column6 │
│ 0 │ red │ orange │ yellow │ green │ blue │ purple │
split chars
將一個字符串分割成一個字符列表
split words
將一個字符串分隔成單詞列表
例如:
$colors | split words
返回:
│ 0 │ red │
│ 1 │ orange │
│ 2 │ yellow │
│ 3 │ green │
│ 4 │ blue │
│ 5 │ purple │
可以用 help split 來顯示完整的命令
str 命令
許多字符串函數是 str 命令的子命令.
可以用 help str 來顯示完整的命令
str contains
檢查字符串中是否包含某個字符或子串
例如:
"hello world" | str contains 'w' #返回: true
"hello world" | str contains 'wor' #返回: true
"hello world" | str contains 'xor' #返回: false
可以忽略大小寫:
"hello world" | str contains 'W' #返回: false
"hello world" | str contains -i 'W' #返回: true
str trim
可以修剪字符串兩邊的空白
例如:
' My string ' | str trim
返回
My string
帶 -l 或 --left 參數以指定只修剪左側
帶 -r 或 --right 參數以指定只修剪右側
str substring
截取子串
例如:
'Hello World!' | str substring 4..8 #o Wo
'abcd1234' | str substring 2.. #cd1234
'abcd1234' | str substring ..4 #abcd
范圍中, 還可以用負數, 類似于 Python. 例如
'abcd1234' | str substring ..-2 #abcd12
'abcd1234' | str substring ..-3 #abcd1
取最右邊的 3 個字符, 也類似 Python
'abcd12345678' | str substring (-3..) #678
上述語句中:
- 因為首字為負數, 因此范圍必須用圓括號 (), 否則被當成 flag
- (-3..) 意思是, 從倒數第三個位置開始取, 直到末尾. 即等于 (-3, -2, -1)
- (-3..-1) 意思是, 從倒數第三個位置開始取, 直到倒數第一. 即等于 (-3, -2)
str start-with / str end-with
str start-with 命令可以判斷字符串是否以指定的子串開頭
str end-with 命令可以判斷字符串是否以指定的子串結尾
例如:
"hello world" | str starts-with 'xor'
"hello world" | str starts-with 'h'
"hello world" | str starts-with 'he'
"hello world" | str ends-with 'd'
"hello world" | str ends-with 'ld'
"hello world" | str ends-with 'orld'
str index-of sub
str index-of sub 命令可以返回子串在長串中的位置. 例如:
'123.456' | str index-of '1'
如果想反向搜索-從字符串尾部開始搜索, 加上參數 -e. 例如:
'123.4156' | str index-of '1' -e
-e 寫前面也是可以的. 例如
'123.4156' | str index-of -e '1'
如果找到, 返回 0 開始的索引; 如果找不到, 返回 -1
str replace
str replace 'old' 'new' 命令可以進行子串替換
例如:
"hello world" | str replace 'hello' 'good'
返回:
good world
str upcase / str downcase : 大小寫轉換
'NU' | str downcase # nu
'nu' | str upcase # NU
str length : 字符串長度
'Hello World!' | str length #12
字符串轉換
有多種方法可以將字符串轉換為其他類型,或者反過來.
轉換為字符串的若干方法:
- 使用 into string. 例如 123 | into string
- 通過字符串插值. 例如 $'(123)'
- 使用 build-string. 例如 build-string (123)
字符串轉換為其他類型:
使用 into <type>. 例如
'123' | into int
path 命令
path 用來探索和維護文件路徑.
使用 path 命令時, 需要帶上子命令.
例如
'a:\tmp\2\aa\readme.txt' | path basename
將返回 readme.txt
path 支持如下子命令
path basename
返回 strPath 的最后一個元素, 一般是文件名 readme.txt
path dirname
返回 strPath 的父目錄
path exists
檢查文件是否存在
path expand
相對路徑轉為絕對路徑
path join
把 list/records 轉換成 字符串
path parse
把 strPath 轉換成結構化的 records 類型的數據
例如
│ prefix │ a: │
│ parent │ a:\tmp\2\aa │
│ stem │ abc │
│ extension │ txt │
path relative-to
path split
把 strPath 切分成 list, 每個元素都是最小成分
例如
│ 0 │ a:\ │
│ 1 │ tmp │
│ 2 │ 2 │
│ 3 │ aa │
│ 4 │ abc.txt │
path type
返回路徑的類型, 比如 file, dir, symlink 等. 輸入參數 (strPath) 必須在盤上存在.
系統命令
Nushell 內置了一批系統命令, 主要有
- 關于文件和目錄: 復制文件, 移動文件, 刪除文件, 創建目錄, 刪除目錄.
特別提出的是, 支持修改文件時間, 監視文件變更, 搜索文件.
搜索文件還支持兩種場景:
1) 根據確定的文件名, 搜索文件位置和類型. 適用場景: 文件名確定, 但文件位置不確定
2) 在指定目錄下, 按通配符搜索. 適用場景: 目錄確定, 通配符確定.
- 目錄變更, 目錄列舉
- 打開文件/保存文件. 特別點贊的是, 打開文件還支持 JSON/XML 等常規的結構化文件.
- 關于進程: 列舉進程, 執行外部進程, 用默認執行器打開指定文件/打開文件夾/打開 URL
- 注冊表查詢
復制文件 - cp
cp 命令的完整格式:
cp {flags} (source) (destination)
Flags:
--recursive, -r : 遞歸子目錄
--verbose, -v : 顯示每個處理結果
--update, -u : 僅當 source 比 dest 新時, 或者 dest 不存在時, 才復制
--interactive, -i : 交互式, 每個文件都問一下
--no-symlink, -n : 忽略快捷方式或符號鏈接
--progress, -p : 顯示進度條
例子:
簡單復制文件
cp a.txt b.txt
遞歸子目錄
cp -r dir_a dir_b
遞歸子目錄, 并且顯示每個處理結果
cp -r -v dir_a dir_b
通配符:
cp *.txt dir_a
僅當原文件比目標文件新時, 才復制
cp -u a b
移動文件 - mv
mv 命令的完整格式:
mv {flags} (source) (destination)
Flags:
--force, -f : 強制覆蓋目標文件
--verbose, -v : 顯示每個處理結果
--update, -u : 僅當 source 比 dest 新時 (此時務必 -f), 或者 dest 不存在時, 才移動
--interactive, -i : 交互式, 每個文件都問一下
例子:
簡單移動文件
mv a.txt b.txt
移動到子目錄下
mv a.txt dir_a\dir_b
通配符:
mv *.txt dir_a
僅當原文件比目標文件新時, 才移動
mv -u a b
刪除文件 - rm
rm 命令的完整格式:
rm {flags} (filename) ...rest
Flags:
--recursive, -r : 遞歸子目錄
--verbose, -v : 顯示每個處理結果
--trash, -t : 移到平臺的回收站, 不是永久刪除. 對 Android 和 ios 不適用
--permanent, -p : 永久刪除. 也忽略 'always_trash' 配置項
--force, -f : 抑制錯誤信息 (即是文件不存在, 也不要告訴我)
--interactive, -i : 交互式, 每個文件都問一下
--interactive-once, -I : 交互式, 但是只問一次
例子:
簡單刪除文件
rm a.txt
移動到回收站
rm --trash a.txt
永久刪除
rm -p *.txt
強行刪除, 忽略 文件不存在 的警告
rm -f *.txt
刪除當前目錄中, 文件長度為 0 的所有文件
ls | where size == 0KB and type == file | each { rm -t $in.name }
創建或修改文件時間 - touch
既能創建一個空白文件, 也能修改文件時間
touch 命令的完整格式:
touch {flags} (filename) ...rest
Flags:
--reference, -r {to} : 把所有文件的修改時間都同步成與 to 一致
--modified, -m : 更新文件的最后修改時間. 如果未指定參數, 就把修改時間更新為當前時間
--access, -a : 更新文件的最后訪問時間. 如果未指定參數, 就把修改時間更新為當前時間
--no-create, -c : 如果文件不存在, 就不要創建文件了
例子:
簡單創建文件
touch a.txt
同時創建多個文件
touch a.txt b.txt c.json
更新文件的修改時間, 改為當前時間
touch -m a.txt
更新文件的修改時間, 改為昨天
touch -m -d "yesterday" *.txt
把文件的更新時間同步到與 test 一致
touch -m -r test a.txt b.txt c.txt
把文件的最后訪問時間改為指定時間
touch -a -d "August 24, 2023; 17:12:56" a.txt b.txt c.txt
搜索文件 - which
which 用于搜索可執行文件或 DLL 的位置. 需要給定一個確切的文件名, 不能是通配符.
貌似是在環境變量之 PATH 給出的列表中搜索
which 命令的完整格式:
which {flags} (filename) ...rest
Flags:
--all, -a : 列出所有的可執行文件
例子:
which cmd.exe
返回:
│ # │ command │ path │ type │
│ 0 │ cmd.exe │ C:\WINDOWS\system32\cmd.exe │ external │
which gdi32.dll
返回:
│ # │ command │ path │ type │
│ 0 │ gdi32.dll │ C:\WINDOWS\system32\gdi32.dll │ external │
打開目錄/文件/URL - start
start 可以用默認的應用程序或 Viewer 來打開: 目錄, 文件, 或 URL
start 命令的完整格式:
start (path)
例子:
start a.txt
start b.jpg
用默認的文件管理器打開當前目錄
start .
用默認的瀏覽器打開 website
start www.ibm.com
執行外部命令 - run-external
run-external 命令的完整格式:
run-external {flags} (command) ...rest
Flags:
--redirect-stdout : 把 stdout 重定向到 pipeline
--redirect-stderr : 把 stderr 重定向到 pipeline
--redirect-combine : 把 stdout 和 stderr 都重定向到 pipeline
--trim-end-newline : 刪除尾部的新行
例子:
run-external "echo" "-n" "hello"
返回:
-n hello
例子:
run-external --redirect-stdout "echo" "-n" "hello" | split chars
返回:
│ 0 │ - │
│ 1 │ n │
│ 2 │ │
│ 3 │ h │
│ 4 │ e │
│ 5 │ l │
│ 6 │ l │
│ 7 │ o │
│ 8 │ │
│ 9 │ │
│ │ │
明顯看到, 上述返回結果中, 有空白字符
修改命令如下, 可以刪除空白字符
run-external --redirect-stdout --trim-end-newline "echo" "-n" "hello" | split chars
返回:
│ 0 │ - │
│ 1 │ n │
│ 2 │ │
│ 3 │ h │
│ 4 │ e │
│ 5 │ l │
│ 6 │ l │
│ 7 │ o │
文件 IO - open/save
打開文件 - open
這個操作很神奇, 能根據擴展名判斷文件類型, 然后把文件內容解釋為表格 (table),隨后就可以用命令來操縱表格啦
擴展名最好是小寫.
如果無法自動判斷, 可以用 from 命令作為管道來強行指定類型
如果編碼不正確, 可以用 decode 命令作為管道.
如何處理結果? - Filter
打開文件之后, 返回的結果, 要么是半結構化的, 要么是全結構化的. 這時就需要用 filter 命令來處理結果. 參見章節: 常用的 filter 命令
open 命令的完整格式:
open {flags} (filename) ...rest
Flags:
--raw, -r: 作為原始格式打開
關于 from 命令支持的文件格式, 參見章節: from 命令
例子:
打開文本文件
open aa.txt
打開 json 文件
open aa.json
打開文件, 并強行作為 json 來解釋
open aa.json | from json
打開文件, 并指定編碼:
open aa.txt -- raw | decode utf-8
open aa.txt -- raw | decode GB18030
可以用如下命令來查看結果的格式
open package.json | describe
結果為 (刪除了一部分, 否則實在太長, 分行是我手工加的):
record <
name: string, publisher: string, description: string, displayName: string,
engines: record <vscode: string>,
categories: list <string>,
activationEvents: list <string>,
capabilities: record <virtualWorkspaces: bool, untrustedWorkspaces: record <supported: bool>>,
contributes: record <configuration: record <...>>,
taskDefinitions: table <
type: string,
required: list <string>,
properties: record <...>
when: string>
>,
repository: record <type: string, url: string>
>
結構非常復雜...
from 命令
把字符串或 BIN 數據解釋成結構化的數據
目前支持以下子命令
Subcommands:
from csv - 解釋為 .csv, 然后創建 table.
from json - 解釋為 json, 然后轉化為結構化數據
from nuon - 解釋為 nuon, 然后轉化為結構化數據
from ods - 解釋為 OpenDocument 的數據表 (.ods), 然后創建 table.
from ssv - 解釋為空白分隔的數據, 然后創建 table. 默認的空白間隔是 2
from toml - 解釋為 tomo, 然后創建 record
from tsv - 解釋為 tsv, 然后創建 table.
from xlsx - 解釋為 Excel (.xlsx), 然后創建 table
from xml - 解釋為 xml, 然后創建 record.
from yaml - 解釋為 yaml, 然后創建 table
from yml - 同 yaml
保存文件 - save
save 命令的完整格式:
save {flags} (filename)
Flags:
--raw, -r : 作為原始格式保存
--append, -a : 把結果添加到文件結尾
--force, -f : 覆蓋目標文件
--progress, -p : 限制進度條
--stderr, -e {path} : 把錯誤輸出重定向到 path 指定的文件中
例子:
把字符串保存到文件中
"save me" | save aa.txt
把字符串添加到文件中
"append me" | save --append aa.txt
把 list 保存到文件中
[a b c d x y] | save aa.json
運行程序, 并且把 stderr 保存到文件中 (這兩個語句沒弄懂差別在哪)
do -i {} | save foo.txt --stderr foo.txt
do -i {} | save foo.txt --stderr bar.txt
save 文件前, 也許希望把數據轉換成指定的格式. 可以用下面的 to 命令 (與 from 相反)
to 命令
把結構化的數據轉換成指定格式
子命令與 from 中的子命令相同
例子:
ls | to json | save "aa.txt"
常用的 Filter 命令
1. each for filters
each 的輸入參數為 list 或 table 的每一行,
用這個參數來運行隨后的閉包,
運行完畢后, 所有結果再形成一個新的 list
完整格式:
each {flags} (closure)
Flags:
--keep-empty, -k : 保留空白結果
例子:
[1 2 3] | each {|e| 2 * $e }
輸出:
│ 0 │ 2 │
│ 1 │ 4 │
│ 2 │ 6 │
掃描輸入的 list, 如果發現 2, 就在輸出的 list 中返回 found (否則就啥都不做):
[1 2 3 2 1] | each {|it| if $it == 2 { "found"}}
輸出:
│ 0 │ found │
│ 1 │ found │
上述命令, 如果用 -k 參數:
[1 2 3 2 1] | each-k {|it| if $it == 2 { "found"}}
結果如下:
│ 0 │ │
│ 1 │ found │
│ 2 │ │
│ 3 │ found │
│ 4 │ │
以下例子引用了索引值 (參見 enumerate for filters)
[1 2 3 2 1] | enumerate | each {|e| if $e.item == 2 { $"found 2 at ($e.index)!"} }
輸出:
│ 0 │ found 2 at 1! │
│ 1 │ found 2 at 3! │
列出目錄中的文件名, 并且把文件名存入另一個文件中
ls | each {|e| $e.name} | save -f a:\aa.txt
注意:
因為 table 是由 record 組成的 list, 因此, 如果對一個 table 調用 each,
那么傳遞給閉包的參數將是一個 record, 而不是一個 cell.
另外, 也要避免將一個 record 傳遞給 each. 因為一個 record 只有一行.
如果將 record 傳遞給 each, each 將只運行一次, 而不是把記錄中的每個元素運行一次 !
如果非要迭代 record, 可以先把 record 轉換成 table, 再迭代這個 table. 例如
{name: sam, rank: 10} | transpose key value
返回如下 table:
│ # │ key │ value │
│ 0 │ name │ sam │
│ 1 │ rank │ 10 │
2. enumerate for filters
某些情況下, 我們希望迭代時, 除了值以外, 還能獲得索引. enumerate 可以滿足這種要求.
對一個 list 執行 enumerate, 可以返回一個 table, 其中 index 是索引, item 是值
[1 2 3 2 1] | enumerate | describe
返回:
table <index: int, item: int>
3. where for filters
可以用于 list, table, range
用于 list 時, 返回 list
用于 table 時, 返回 table
用于 range 時, 返回 list
例子:
1..5 | where {|x| $x > 2}
返回
│ 0 │ 3 │
│ 1 │ 4 │
│ 2 │ 5 │
[1, 2, 3, 4, 5] | where {|x| $x > 2}
也返回
│ 0 │ 3 │
│ 1 │ 4 │
│ 2 │ 5 │
4. length for filters
返回 list 或 table 的數量.
例子:
[a b c d] | length # 4
注意:
length 不適用于字符串, 如果希望獲得字符串的長度, 請用 str length
5. select for filters
選擇指定的列.
既可以用于 record, 也可以用于 table, 還可以用于 list
參數可以是列名, 也可以是數字
例如:
ls | select name
ls | select 0 1 2 3 # 選擇開始的 4 行, 等效于 ls | first 4
與 get 不同的是, 用于 table 時, 返回還是 table, 用于 list 時, 返回還是 list
例如, 比較如下兩行
[a b c d] | select 1 # [b]
[a b c d] | get 1 # b
前者返回一個 list, 但只有一個元素: [b]. 后者直接返回值
如果超過 1 個, 兩者返回相同的類型
[a b c d] | select 1 2 # [b c]
[a b c d] | get 1 2 # [b c]
6. get for filters
抽取數據
既可以用于 record, 也可以用于 table, 還可以用于 list
例子:
ls | get name # 返回 list <name>
ls | get 2.name # 返回 string
ls | get name.2 # 返回 string, 與上相同
ls | get name | get 2 # 返回 string, 與上相同
ls | get 2 | get name # 返回 string, 與上相同
get 語法還可以縮寫為如下形式, 相當于用索引訪問 list
[a b c].2 # c
7. items for filters
給定一個 record, 迭代每個 (key, value)
完整的語法為:
items (closure)
例子:
ls | get 2 | items {|key, value| echo $'($key) = ($value)' }
返回:
name = DB.txt
type = file
size = 40 B
modified = Mon, 16 Oct 2023 12:15:34 +0800 (a day ago)
8. lines for filters
把輸入轉換為 list <string>. 以換行作為分隔符.
對 open 打開的 raw text file 特別有用, 把長串轉換為短串 list
完整語法為:
lines {flags}
Flags:
--skip-empty, -s : 跳過空行
例子:
"two\n\nlines" | lines
返回
│ 0 │ two │
│ 1 │ │
│ 2 │ lines │
"two\nlines" | lines
返回:
│ 0 │ two │
│ 1 │ lines │
"two\n\nlines" | lines -s
返回:
│ 0 │ two │
│ 1 │ lines │
9. values for filters
給定一個 record 或 table, 用 values 過濾器可以生成一個 list, 每個元素來自于指定的列值
例子:
ls | get 2 | values
返回
│ 0 │ DB.txt │
│ 1 │ file │
│ 2 │ 40 B │
│ 3 │ a day ago │
如果輸入是一個 table, 將生成 list <list <...>>.
例如:
ls | values
注:
與此對應的是 columns, 用 columns 過濾器可以生成一個 list, 每個元素來自于指定的列名
10. columns for filters
給定一個 record 或 table, 用 columns 過濾器可以生成一個 list, 每個元素來自于指定的列名
例子:
ls | get 2 | values
輸出:
│ 0 │ name │
│ 1 │ type │
│ 2 │ size │
│ 3 │ modified │
11. 對 list 或 table 的 "掐頭去尾"
skip : 忽略開頭的 N 行, 如果無參數, 默認跳過開頭的第一行
drop : 忽略結尾的 N 行, 如果無參數, 默認跳過結尾的最后一行
first: 返回開頭的 N 行, 如果無參數, 默認返回第一行
last : 返回結尾的 N 行, 如果無參數, 默認返回最后一行
輸出與輸入相同: 對 list 返回 list, 對 table 返回 table
例子:
ls | first
ls | last 3
ls | skip
ls | drop 6
[a b c d ] | drop 2
返回:
│ 0 │ a │
│ 1 │ b │
12. 對 list 或 table, 用區間指定選擇范圍
除了掐頭去尾以外, 有時我們還希望用一個區間來指定選擇范圍. 可以用 range 過濾器來達成這個目的
例子:
[a b c d e f] | range 2..4
返回
│ 0 │ c │
│ 1 │ d │
│ 2 │ e │
返回最后兩項:
[a b c d e f] | range (-2..)
返回
│ 0 │ e │
│ 1 │ f │
返回倒數第3-倒數第2:
[a b c d e f] | range (-3..-2)
返回:
│ 0 │ d │
│ 1 │ e │
13. uniq for filters - 返回值去重
完整的格式:
uniq {flags}
Flags:
--count, -c: 返回 table, 其中 value 列給出去重后的值, count 列給出重復次數
--repeated, -d: 僅返回重復出現的項 (即 count > 1 的項), 與 -u 相反
--unique, -u: 僅返回出現一次的項 (即 count = 1 的項), 與 -d 相反
--ignore-case, -i: 忽略大小寫
例子:
[a b a x b w c x] | uniq
返回:
│ 0 │ a │
│ 1 │ b │
│ 2 │ x │
│ 3 │ w │
│ 4 │ c │
[a b a x b w c x] | uniq -c
返回:
│ # │ value │ count │
│ 0 │ a │ 2 │
│ 1 │ b │ 2 │
│ 2 │ x │ 2 │
│ 3 │ w │ 1 │
│ 4 │ c │ 1 │
[a b a x b w c x] | uniq -d
返回:
│ 0 │ a │
│ 1 │ b │
│ 2 │ x │
[a b a x b w c x] | uniq -u
返回:
│ 0 │ w │
│ 1 │ c │
14. uniq-by for filters - 返回值去重
如果輸入是 table, 我們希望可以按列名來去重, 這時可以用 uniq-by 過濾器. 參數中可以指定一個或多個列
完整格式:
uniq-by {flags} ...rest
Flags 與 uniq 相同:
例子:
按 fruit 去重
[[fruit count]; [apple 9] [apple 2] [pear 3] [orange 2]] | uniq-by fruit
返回:
│ # │ fruit │ count │
│ 0 │ apple │ 9 │
│ 1 │ pear │ 3 │
│ 2 │ orange │ 2 │
按 count 去重
[[fruit count]; [apple 9] [apple 2] [pear 3] [orange 2]] | uniq-by count
返回:
│ # │ fruit │ count │
│ 0 │ apple │ 9 │
│ 1 │ apple │ 2 │
│ 2 │ pear │ 3 │
15. sort for filters
排序.
適用于 list 或 record
完整格式:
sort {flags}
Flags:
--reverse, -r: 逆序
--ignore-case, -i: 忽略大小寫
--values, -v: 如果輸入是單個 record, 將按其 value 排序; 否則忽略此選項
--natural, -n: 如果輸入項是字符串組成的數字, 將轉成數字來排序
例子:
[2 0 1] | sort
返回:
│ 0 │ 0 │
│ 1 │ 1 │
│ 2 │ 2 │
忽略大小寫排序
[airplane Truck Car] | sort -i
返回:
│ 0 │ airplane │
│ 1 │ Car │
│ 2 │ Truck │
record 排序, 按名稱
{b: 4, a: 3, c:1} | sort
返回:
│ a │ 3 │
│ b │ 4 │
│ c │ 1 │
record 排序, 按值
{b: 4, a: 3, c:1} | sort -v
返回:
│ c │ 1 │
│ a │ 3 │
│ b │ 4 │
按字母順序排序
["4", "300", "100"] | sort
返回:
│ 0 │ 100 │
│ 1 │ 300 │
│ 2 │ 4 │
改成按數字順序排序:
["4", "300", "100"] | sort -n
返回:
│ 0 │ 4 │
│ 1 │ 100 │
│ 2 │ 300 │
16. sort-by for filters
排序.
適用于 table
完整格式:
sort-by {flags} ...rest
Flags:
--reverse, -r: 逆序
--ignore-case, -i: 忽略大小寫
--natural, -n: 如果輸入項是字符串組成的數字, 將轉成數字來排序
例子
ls | sort-by modified
[[fruit count]; [apple 9] [apple 2] [pear 3] [orange 2]] | sort-by fruit
返回:
│ # │ fruit │ count │
│ 0 │ apple │ 9 │
│ 1 │ apple │ 2 │
│ 2 │ orange │ 2 │
│ 3 │ pear │ 3 │
[[fruit count]; [apple 9] [apple 2] [pear 3] [orange 2]] | sort-by count
返回:
│ # │ fruit │ count │
│ 0 │ apple │ 2 │
│ 1 │ orange │ 2 │
│ 2 │ pear │ 3 │
│ 3 │ apple │ 9 │
17. find for filters
搜索
完整格式:
find {flags} ...rest
Flags:
--regex, -r {string}: 用正則表達式來匹配
--ignore-case, -i: 忽略大小寫的正則; 等效于 (?i)
--multiline, -m: 多行正則模式: 用 ^ and $ 來匹配行初/行尾; 等效于 (?m)
--dotall, -s: dotall 正則模式; 等效于 (?s)
--columns, -c {list<string>}: 指定用哪些列名來搜索, 不支持正則
--invert, -v: 反向匹配
例子:
字符串中搜索
"abcdef" | find b
返回:
abcdef
在 file size 的 list 中搜索
[1 5 3kb 4 3Mb] | find 5 3kb
返回:
│ 0 │ 5 │
│ 1 │ 2.9 KiB │
用正則來搜索
[abc bde arc abf] | find --regex "ab"
返回:
│ 0 │ abc │
│ 1 │ abf │
用列名來搜索
ls | find -c [name] age
返回:
│ # │ name │ type │ size │ modified │
│ 0 │ package.json │ file │ 1.9 KB │ a day ago │
18. is-empty for filters
用來判斷某個項是否為空.
輸入參數可以是: 字符串, list, table
完整格式:
is-empty ...rest
參數可以輸入列名, 用于判斷特定的某一列
例子:
ls | is-empty
[[meal size]; [arepa, null] ] | is-empty meal # false
[[meal size]; [arepa, null] ] | is-empty size # true
系統或平臺相關的命令
1. sleep - 等待/延遲
完整格式:
sleep (duration) ...rest
例子:
sleep 1sec # 延遲 1秒
sleep 3sec 5min # 延遲 5分 + 3秒
2. ps - 顯示進程信息
完整格式:
ps {flags}
Flags:
--long, -l: 顯示盡可能多的列
如果不加選項, ps 輸出如下 table
table <pid: int, ppid: int, name: string, cpu: float, mem: filesize, virtual: filesize>
如果加上選項 -l, ps 輸出如下 table:
table <pid: int, ppid: int, name: string, cpu: float, mem: filesize, virtual: filesize,
command: string, start_time: date, user: string, user_sid: string, priority: int,
cwd: string, environment: list<string>>
針對 Windows 平臺:
- mem : 對應了任務管理器中的 WorkingSet
- command : App 的完整名稱: FullPath + App Name
- user : 用戶名
- user_sid : 用戶的 GUID
- cwd : 此文件所在的目錄
- environment : 運行環境
例子:
顯示占用內存最多的最后 5 個
ps | sort-by mem | last 5
可能的輸出:
│ # │ pid │ ppid │ name │ cpu │ mem │ virtual │
│ 0 │ 17896 │ 17312 │ chrome.exe │ 0.00 │ 244.7 MB │ 154.3 MB │
│ 1 │ 17240 │ 3000 │ msedge.exe │ 0.00 │ 276.4 MB │ 166.9 MB │
│ 2 │ 15840 │ 3000 │ WXWork.exe │ 0.00 │ 376.7 MB │ 396.5 MB │
│ 3 │ 14132 │ 3000 │ WeChat.exe │ 0.00 │ 430.8 MB │ 363.7 MB │
│ 4 │ 3000 │ 5536 │ Explorer.EXE │ 0.00 │ 513.0 MB │ 1.7 GB │
3. kill - 殺死指定的進程
完整格式
kill {flags} pid ...rest
Flags:
--force, -f: 強行殺死
--quiet, -q: 安靜! 不要在 Console 上顯示任何信息
kill 通常與 ps 一起使用, 以便根據 name 獲得 pid
例子:
殺死占用內存最多的進程:
ps | sort-by mem | last | kill $in.pid
殺死含有指定名稱的所有進程:
ps | where ($it.name | str contains -i "code.exe") | get pid | each {|it| kill -f $it }
上述語句中, where 后面的圓括號不能省略
核心命令
1. 用 let 定義變量
在 nu 中, 可以用 let 來定義一個 '變量'. 但其實是定義了一個常量, 一旦賦值就不能修改.
但是可以重新定義.
創建常量后, 可以通過 $來引用
以下語句是合法的
let x = 0; echo $x; let x = 'hello'; echo $x
另外, 變量是有作用域的, 在嵌套塊中, 可以定義同名的變量, 這個變量不會影響上層的同名變量.
例如:
let my_value = 4
do { let my_value = 5; echo $my_value }
echo $my_value
先輸出 5, 然后輸出 4
后續語句不能修改的變量, 不能用于循環控制. 因此, 我們需要一個常規編程語言意義上的變量.
這種情況下, 就要用 mut 來定義真正意義上的變量
2. 用 mut 定義變量
例如:
mut a = 1; $a = $a + 10; echo $a
將輸出 11
有了真正的 '變量' 之后, 就可以開始探索循環了
3. for 循環
for 的完整格式:
for {flags} (var_name) (range) (block)
Flags:
--numered, -n : 同時返回索引和值 ($it.index 和 $it.item)
參數:
var_name : 循環變量名
range : 循環的范圍
block : 要運行的語句塊
例子:
for x in [1 2 3] { print $x * $x}
for $x in 1..4 { print $x}
上述兩個語句, 循環變量用 x 和 $x 都可以
舉一個同時引用索引和值的例子:
for -n $it in ['a' 'b' 'c' 'd'] { print $"[($it.index)] is ($it.item)" }
輸出:
[0] is a
[1] is b
[2] is c
[3] is d
之前的 ls 語句還可以寫成這樣:
for it in (ls | get name) { print $it }
或者寫成這樣, 更簡短易懂
for it in (ls).name { print $it }
4. loop 循環
loop 的完整格式:
loop (block)
參數:
block : 要運行的語句塊
loop 的語法特別簡單(粗暴): 參數只有一個語句塊.
通常情況下, 需要配合 mut 定義的變量來控制循環何時結束
例子:
mut x = 0; loop { if $x > 10 { break }; $x = $x + 1 }; $x
5. while 循環
while 的完整格式:
while (cond) (block)
參數:
cond : 要檢測的條件
block : 要運行的語句塊
通常情況下, 需要配合 mut 定義的變量來控制循環何時結束
例子:
mut x = 0; while $x < 10 { $x = $x + 1; print $"running on ($x)" }
6. do 語句
do 語句用于執行一個閉包, 自動把管道輸入 ($in) 作為參數傳入
完整語法為:
do {flags} (closure) ...rest
Flags:
--ignore-errors, -i: 閉包運行時, 忽略其錯誤
--ignore-shell-errors, -s: 閉包運行時, 忽略 shell 錯誤
--ignore-program-errors, -p: 閉包運行時, 忽略外部程序錯誤
--capture-errors, -c: 閉包運行時, 捕獲錯誤, 并且返回錯誤
參數:
closure : 要運行的閉包
...rest : 給閉包的參數
例子:
運行閉包:
do { echo hello }
把閉包保存為變量, 然后運行閉包:
let text = "I am enclosed"; let hello = {|| echo $ text}; do $hello
帶參數的閉包:
do {|x| 100 + $x } 77
從輸入管道中獲取數據
77 | do {|| 100 + $in }
上述語句中, $in 是系統自動傳入的管道
幾個綜合應用的例子
1. 列出目錄下的所有文件, 遞歸子目錄
如果文件名中包含 hello, 就把這些文件復制到另一個目錄下, 同時顯示這些文件名
ls -f **/*.* | where ($it.name | str contains -i "txt") | get name | each {|it| cp -v $it "a:\\tmp25"}
如果希望把滿足條件的文件名保存起來, 可以利用 each 的特征: 把結果合成一個 list
ls -f **/*.* | where ($it.name | str contains -i "txt") | get name | each {|it| cp -v $it "a:\\tmp25"; $it} | save -f "a:\\tmp25\\out.txt"
寫成如下語句也可以:
ls -f **/*.* | get name | each {|it| if ($it | str contains -i "txt") {$it}} | each {|it| cp -v $it "a:\\tmp25"; $it} | save -f "a:\\tmp25\\out.txt"
或者
ls -f **/*.* | get name | where (str contains -i "txt") | each {|it| cp -v $it "a:\\tmp25"; $it} | save -f "a:\\tmp25\\out.txt"
2. 中的情形, 把不滿足條件的文件作為列表, 存為另一個文件
ls -f **/*.* | where not ($it.name | str contains -i "txt") | get name | save -f "a:\\tmp25\\not.txt"
3. 搜索一系列文件, 對每個文件進行模式匹配
軟件開發過程和產品生命周期中, 以下場景不可避免:
- 功能逐漸增加
- 需求不斷變更
- 越來越多的客戶化和定制
隨著代碼和工程項目的分支越來越多, 版本也就越來越多, "這個功能我做過了,但是,代碼到底在哪個該死的版本中?", 這個問題經常問.
我們可以用 Nu 來編寫腳本, 根據蛛絲馬跡, 從浩如煙海的源文件中, 找到文件名和修改時間.
3.1 首先設計一個從文件中搜索字符串的自定義函數
book 中叫自定義命令, 我更喜歡按傳統編程語言思路, 叫做函數.
1 def on-file [filename: string subString: string] { 2 #print $"processing ($filename)" 3 let ln = open $filename -r | decode GB18030 | lines; 4 for -n $it in ($ln) { 5 if ($it.item | str contains -i $subString) { 6 print $"Found at line ($it.index) of file ($filename)"; 7 return true 8 } 9 }; 10 return false; 11 }
輸入參數是兩個: 文件名和待匹配的字符串
行 3: 定義一個變量, 保存打開文件后的行列表
行 4: 用 for 迭代, flag 為 -n, 以便獲得迭代索引
行 5,6,7: 如果字符串匹配成功, 就顯示行位置和文件名,然后提前返回
以下是單元測試代碼:
on-file "d:\\works\\main.cpp" "RemoveFile"
返回:
Found at line 13 of file main.cpp
3.2 在 ls 命令中, 把上述輔助函數掛到管道 | 中, 就可以搜索整個目錄了.
代碼如下:
ls **\*.cpp | get name | each {|it| if (on-file $it "RemoveTempFile") {$it} }
在我機器上的運行結果如下:
Found at line 131 of file Works.51\MainWindow.InitExit.cpp
│ 0 │ Works.51\MainWindow.InitExit.cpp │
Found at line 63 of file Works.51\Tool\Utility.TempFile.cpp
Found at line 245 of file Works.51\Test\TestFrameWnd.Show.cpp
│ 1 │ Works.51\Tool\Utility.TempFile.cpp │
│ 2 │ Works.51\TestFrameWnd.Show.cpp │
Found at line 149 of file Works.51\Test.InitExit.cpp
│ 3 │ Works.51\Test.InitExit.cpp │
print 的結果與最終 list 混合在一起了, 因為 3.2 代碼里面, each 每次迭代都執行一遍 print, 之后把管道輸出的 list 的變更也輸出
3.3 換一個思路, 把 on-file 改成從管道獲得文件名
1 def on-file [subString: string] { 2 let filename = $in; 3 #print $"processing ($filename)" 4 let lines = open $filename -r | decode GB18030 | lines; 5 for -n $it in ($lines) { 6 if ($it.item | str contains -i $subString) { 7 print $"Found at line ($it.index) of file ($filename)"; 8 return true 9 } 10 }; 11 return false; 12 }
現在, 輸入參數只有一個了, 就是待匹配的子串. 而文件名來自管道輸入
行 2, 把管道輸入保留在 filename 變量中.
因為有兩個地方要引用文件名 (如果包括注釋就是三個引用).
函數中如果多個地方引用 $in, 所有后續的引用, 要么為 null, 要么發生代碼解釋錯誤
單元測試代碼需要改成:
"d:\\works\\main.cpp" | on-file "RemoveFile"
3.4 ls 命令改成:
ls **\*.cpp | where ($it.name | on-file "RemoveTempFile") | select name
在我機器上的運行結果如下:
Found at line 131 of file Works.51\MainWindow.InitExit.cpp
Found at line 63 of file Works.51\Tool\Utility.TempFile.cpp
Found at line 245 of file Works.51\Test\TestFrameWnd.Show.cpp
Found at line 149 of file Works.51\Test.InitExit.cpp
│ # │ name |
│ 0 │ Works.51\MainWindow.InitExit.cpp │
│ 1 │ Works.51\Tool\Utility.TempFile.cpp │
│ 2 │ Works.51\TestFrameWnd.Show.cpp │
│ 3 │ Works.51\Test.InitExit.cpp │
可以看到, 最終的輸出結果是一個 table. 而且不會跟 print 結果混合了.
浙公網安備 33010602011771號