Try   HackMD
tags: PowerShell

PowerShell 學習筆記

說明文件

查看本機的說明文件前, 請先更新說明文件:

Update-Help

接著就可以用 get-help 觀看本機版的說明文件:

Get-Help get-item

以上看到的是簡易版本的說明, 如果要觀看完整的說明文件, 必須加上 -Full 參數:

Get-Help get-item -Full

你也可以查看線上版本的說明文件:

Get-Help get-item -Online

指令語法

說明文件中的語法表示方式:

SYNTAX
    Move-Item [[-Destination] <System.String>] [-Credential <Sys
    tem.Management.Automation.PSCredential>] [-Exclude <System.S
    tring[]>] [-Filter <System.String>] [-Force] [-Include <Syst
    em.String[]>] -LiteralPath <System.String[]> [-PassThru] [-C
    onfirm] [-WhatIf] [<CommonParameters>]

    Move-Item [-Path] <System.String[]> [[-Destination] <System.
    String>] [-Credential <System.Management.Automation.PSCreden
    tial>] [-Exclude <System.String[]>] [-Filter <System.String>
    ] [-Force] [-Include <System.String[]>] [-PassThru] [-Confir
    m] [-WhatIf] [<CommonParameters>]```
  1. 你可以看到顯示的語法有兩組, 這表示有兩組參數集, 彼此不能隨意混用。舉例來說, 如果使用了上方參數集獨有的 -LiteralPath 參數, 就不能同時使用下方參數集才有的 -Path 參數。

  2. 每個參數都是 -參數名稱 參數值 的形式

  3. 沒有被中括號括住的參數就是強制參數, 一定要有, 例如:

    ​​​​[-Path] <String[]>
    

    這個 Path 參數就是強制參數, 不過參數名稱有用中括號括起來, 表示指定此參數時, 可以不用加上 -Path 指定參數名稱。

  4. 有用中括號括起來的參數就是可選用的參數, 例如:

    ​​​​[-Filter <String>]
    

    就表示 Filter 參數並不一定要有, 需要時才指定。

  5. 選用參數的參數名稱若有再使用中括號括起來, 稱為位置參數, 表示指定該參數時, 並不一定要指定參數名稱, 但若是指令可接受多個位置參數時, 必須依照其在語法說明中的順序出現, 例如:

    ​​​​[[-Destination] <String>]
    

    也就是說, 下指令時, 若有多個不具名參數, 就會依照順序一一對應到語法中的個別位置參數。若該指令有強制參數, 那麼第 1 個不具名參數就會對應到強制參數。

  6. 參數值具有型別, 若型別後面還跟著一對中括號, 表示該參數值可以接受單一資料或是資料清單 (陣列), 例如:

    ​​​​[-Path] <String[]>
    

    若是資料清單, 清單中的個別項目以逗號相隔, 單一資料內含空白時, 必須以 "" 括起來, 例如:

    ​​​​Move-Item a.txt,"new file.txt" ..
    
  7. 有些參數只有名稱沒有值, 稱為開關 (switch) 參數, 代表 True 或是 False 的值, 例如:

    ​​​​[-Confirm]
    

    如果查看完整的說明文件, 可以看到這個參數的進一步說明:

    ​​​​-Confirm [<SwitchParameter>]
    ​​​​        Prompts you for confirmation before running the cmdlet.
    
    ​​​​        Required?                    false
    ​​​​        Position?                    named
    ​​​​        Default value                False
    ​​​​        Accept pipeline input?       False
    ​​​​        Accept wildcard characters?  false
    

    就可以知道這個參數不指定的話預設值為 False, 指定時代表 True

指令範例

以下可取得特定指令的範例:

get-help Get-Event -Examples

PowerShell 用詞

command

command 代表各種可以執行的事物, 包括以下幾種:

  1. cmdlet:PowerShell (以 .net 撰寫) 原生的指令, 除少數例外, 名稱都是 動詞-名詞 的格式, 像是 Get-Help
  2. function:用 PowerShell 語言撰寫的指令。
  3. application:可執行的應用程式

語法基礎

語法注意要點:

  1. PowerShell 並不區分大小寫。

  2. 參數名稱最少字母原則, 只要能區分參數名稱, 可以不用打完整的名稱, 例如以下兩者功能相同:

    ​​​​> Get-Date -DisplayHint time
    
    ​​​​下午 05:10:18
    ​​​​> Get-Date -Disp time
    
    ​​​​下午 05:10:27
    

    由於沒有其他參數的名稱是 Disp 開頭, 因此打 -Disp 就可以代表 -DisplayHint 參數。

  3. 如果指令太長, 可以用反引號 (backtick, ` 符號) 接續下一行, 例如:

    ​​​​# echo `
    ​​​​hello
    ​​​​hello
    

在指令行中代入指令執行結果

用小括號可以在指令內代入其他指令的執行結果:

> Write-Host hello (Get-Date)
hello 2020/12/16 下午 04:20:17

在字串中代入指令執行結果

利用英文雙引號可以在字串中用 $ 符號代入變數值, 或者用 $() 代入指令執行結果:

echo "time: $(get-date)"
time: 08/13/2021 17:38:22
❯ $name = "John"echo "hello, $name"
hello, John

如果要在字串中表示 $ 符號, 可以加上 ` 字元讓 $ 跳脫置換功能變成一般文字, 或是改用英文單引號來表示字串, 就不會有置換功能了。

字串處理

規則表達式

PowerSehll 提供有 -match-replace 運算器, 可以依據規則表達式比對或是取代字串, 例如:

❯ "john_at_gmail.com" -match "((.+)@(.+))"
False

如果比對成功, 它會自動產生一個雜湊陣列 $Matches, 包含比對相符的內容:

"john@gmail.com" -match "((.+)@(.+))"
True
❯ $Matches

Name                           Value
----                           -----
3                              gmail.com
2                              john
1                              john@gmail.com
0                              john@gmail.com

索引鍵 '0' 為相符的子字串內容, 索引鍵 '1' 開始是從最外層依序到最裡層的群組相符的子字串內容。

-nomatch 則是和 -match 相反的運算器。

你也可以處理字串替換, 例如:

"john@gmail.com" -replace "((.+)@(.+))",'$2_at_$3'
john_at_gmail.com

注意在 PowerShell 中英文雙引號的字串會進行變數置換, 所以在使用規則表達式取代字串時, 最好用英文單引號, 避免以 $ 字元標示的群組編號被當成變數。

Provider

PowerShell 使用 Provider 抽象化系統內儲存的資料, 例如檔案系統、登錄檔、甚至環境變數等等。

以下指令可以得知目前系統上有哪些 Provider:

> Get-PSProvider

Name                 Capabilities                                  Drives
----                 ------------                                  ------
Registry             ShouldProcess                                 {HKLM, HKCU}
Alias                ShouldProcess                                 {Alias}
Environment          ShouldProcess                                 {Env}
FileSystem           Filter, ShouldProcess, Credentials            {C, D, Temp, E…}
Function             ShouldProcess                                 {Function}
Variable             ShouldProcess                                 {Variable}

列出抽象的磁碟

以下指令則可以知道系統上有哪些 (含抽象的) 磁碟機:

> Get-PSDrive

Name           Used (GB)     Free (GB) Provider      Root                                        CurrentLocation
----           ---------     --------- --------      ----                                        ---------------
Alias                                  Alias
C                  85.68          8.88 FileSystem    C:\                                           Users\ShinWei
Cert                                   Certificate   \
D                  95.20         32.66 FileSystem    D:\                                                    temp
E                   3.98          0.00 FileSystem    E:\
Env                                    Environment
F                                      FileSystem    F:\
Function                               Function
HKCU                                   Registry      HKEY_CURRENT_USER
HKLM                                   Registry      HKEY_LOCAL_MACHINE
M                 258.26        596.53 FileSystem    \\192.168.0.244\maker_disk
Temp               85.68          8.88 FileSystem    C:\Users\ShinWei\AppData\Local\Tem…
Variable                               Variable
WSMan                                  WSMan

存取環境變數

你可以像是操作檔案系統的方式操作這些資料, 例如檢視環境變數:

> cd Env:
> Get-Item temp

Name                           Value
----                           -----
TEMP                           C:\Users\ShinWei\AppData\Local\Temp
> Get-Content temp
C:\Users\ShinWei\AppData\Local\Temp

存取登錄檔

也可以這樣操作登錄檔:

> cd HKLM:
> Set-Location .\SOFTWARE\
> cd .\Windows\
> ls

    Hive: HKEY_LOCAL_MACHINE\SOFTWARE\Windows

Name                           Property
----                           --------
CurrentVersion

所有資料都是物件

取得集合中物件的屬性清單:

>Get-Date

2023623日 下午 03:01:37

>Get-Date | Get-Member

  TypeName: System.DateTime

Name                 MemberType     Definition
----                 ----------     ----------
Add                  Method         datetime Add(timespan value)
AddDays              Method         datetime 
...
Year                 Property       int Year {get;}
DateTime             ScriptProperty System.Object DateTime {get=

使用 year 取得年份:

> (get-date).year
2023

所有的指令都是針對物件在操作:

> Get-Item *.html

    Directory: D:\temp

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---        2020/12/15 下午 06:23           1010 index.html
-a---        2020/12/17 上午 09:49          24109 opencv.html

我們可以針對兩個檔案物件排序:

> (Get-Item *.html) | Sort-Object -Property Length -Descending

    Directory: D:\temp

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---        2020/12/17 上午 09:49          24109 opencv.html
-a---        2020/12/15 下午 06:23           1010 index.html

取得物件的屬性:

> get-process | Get-Member

   TypeName: System.Diagnostics.Process

Name                       MemberType     Definition
----                       ----------     ----------
Handles                    AliasProperty  Handles = Handlecount
Name                       AliasProperty  Name = ProcessName
NPM                        AliasProperty  NPM = NonpagedSystemMemorySize64
...

篩選物件的屬性, 只留下指定的屬性 (移除未指定屬性):

> get-process | Select-Object -Property Name | Get-Member

   TypeName: Selected.System.Diagnostics.Process

Name        MemberType   Definition
----        ----------   ----------
Equals      Method       bool Equals(System.Object obj)
GetHashCode Method       int GetHashCode()
GetType     Method       type GetType()
ToString    Method       string ToString()
Name        NoteProperty string Name=ACMON

用指定的屬性替代原始的物件:

> get-process | Select-Object -ExpandProperty Name | Get-Member

   TypeName: System.String

Name                 MemberType            Definition
----                 ----------            ----------
Clone                Method                System.Object Clone(), System.Object ICloneable.Clone()
...

若該屬性是陣列, 就會展開陣列的內容, 例如:

> get-process -Name explorer | Select-Object -ExpandProperty Modules | Select-Object -Property ModuleName

ModuleName
----------
Explorer.EXE
ntdll.dll
...
EFSUTIL.dll
DSROLE.dll
Windows.Internal.System.UserProfile.dll
CloudExperienceHostBroker.dll

這一堆 .dll 就是 Modules 屬性展開後的每一個 Module。

物件預設的顯示格式

請參考這裡

PowerShell 的系統變數

$PROFILE

$PROFILE 變數會記錄目前使用的 PowerSehll 設定檔路徑:

❯ $PROFILE
C:\Users\ShinWei\Documents\PowerShell\Microsoft.PowerShell_profile.ps1

$PSVersionTable

$PSVersionTable 可以取得 PowerShell 版本資訊:

❯ $PSVersionTable

Name                           Value
----                           -----
PSVersion                      7.1.3
PSEdition                      Core
GitCommitId                    7.1.3
OS                             Microsoft Windows 10.0.22000
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

腳本區塊 (Script Block)

腳本區塊是一種功能類似匿名函式的物件, 定義的方式就是用大括號將要執行的內容包起來, 例如:

$s = { echo "hello" }

要叫用定義好的腳本區塊, 必須使用 & 運算器:

❯ & $s
hello

你也可以幫腳本區塊定義引數, 例如:

❯ $s = {
>> param(
>>   [String]$name
>> )
>> echo "hello, $name"
>> }

叫用時可以利用位置傳遞傳數:

❯ & $s "John"
hello, John

也可以指名方式傳遞參數:

& $s -name "John"
hello, John

從字串建立並執行腳本區塊

如果要從字串中記錄的敘述建立並執行腳本區塊, 可以搭配 Invoke-Expression, 例如若我們讀取一個腳本檔案, 並將內容記錄在變數中:

$s
param([String]$name) echo "hello, $name"

如果想使用 $s 的內容建構成腳本執行, 你可能會想這樣做:

$err = { $s }

但這樣建構出來的腳本區塊在叫用時, 是執行 $s, 所以只會顯示字串的內容:

❯ & $err -name Mary
param([String]$name) echo "hello, $name"

正確的作法是先透過字串置換, 然後再使用 Invoke-Expression 來執行叫用腳本區塊:

$correct = "& { $s } -name 'Mary'"
❯ Invoke-Expression $correct
hello, Mary

你也可以省略中間置換字串的變數, 直接寫成一列:

❯ Invoke-Expression "& { $s } -name 'Mary'"
hello, Mary

在底下的更新 PowerShell Core 到最新版中, 就有實際運用的範例, 會從網路下載的腳本檔建立腳本區塊執行。

重新導向與管線

用 ForEach-Object 一行一行取得程式輸出做處理

單純用管線取得程式輸出的話, 會等到程式結束才傳回所有的輸出內容, 如果希望一行一行取得輸出結果處理, 不用等到程式結束, 可以使用 ForEach-Object

❯ ping -t 8.8.8.8 | ForEach-Object -process { $_ -replace '時間', 'time' }

Ping 8.8.8.8 (使用 32 位元組的資料):
回覆自 8.8.8.8: 位元組=32 time=6ms TTL=115
回覆自 8.8.8.8: 位元組=32 time=7ms TTL=115
回覆自 8.8.8.8: 位元組=32 time=5ms TTL=115
回覆自 8.8.8.8: 位元組=32 time=5ms TTL=115

其中 -process 是預設參數, 所以可以省略不寫:

❯ ping -t 8.8.8.8 | ForEach-Object { $_ -replace '時間', 'time' }


Ping 8.8.8.8 (使用 32 位元組的資料):
回覆自 8.8.8.8: 位元組=32 time=11ms TTL=115
回覆自 8.8.8.8: 位元組=32 time=8ms TTL=115
回覆自 8.8.8.8: 位元組=32 time=9ms TTL=115```

另外, %foreachForEach-Object 的別名, 可以縮短指令行:

❯ ping -t 8.8.8.8 | % { $_ -replace '時間', 'time' }

Ping 8.8.8.8 (使用 32 位元組的資料):
回覆自 8.8.8.8: 位元組=32 time=7ms TTL=115
回覆自 8.8.8.8: 位元組=32 time=4ms TTL=115
回覆自 8.8.8.8: 位元組=32 time=8ms TTL=115

如果 foreach 出現在一列的開頭, 會變成 foreach 敘述, 雖然和上面談的 ForEach-Object 內建指令相似, 但它無法一行一行取得程式輸出, 只能接收完整的集合資料後才一行行處理。例如:

❯ foreach($i in (ping 8.8.8.8)) {get-date -disp time; echo $i}

下午 01:33:44

下午 01:33:44
Ping 8.8.8.8 (使用 32 位元組的資料):
下午 01:33:44
回覆自 8.8.8.8: 位元組=32 時間=5ms TTL=115
下午 01:33:44
回覆自 8.8.8.8: 位元組=32 時間=4ms TTL=115
下午 01:33:44
回覆自 8.8.8.8: 位元組=32 時間=8ms TTL=115
下午 01:33:44
回覆自 8.8.8.8: 位元組=32 時間=4ms TTL=115
下午 01:33:44

下午 01:33:44
8.8.8.8 的 Ping 統計資料:
下午 01:33:44
    封包: 已傳送 = 4,已收到 = 4, 已遺失 = 0 (0% 遺失),
下午 01:33:44
大約的來回時間 (毫秒):
下午 01:33:44
    最小值 = 4ms,最大值 = 8ms,平均 = 5ms

可以看到時間都在同一秒, 但若是改用 ForEach-Object

❯ ping 8.8.8.8 | % { Get-Date -disp time; echo $_ }

下午 01:37:18

下午 01:37:18
Ping 8.8.8.8 (使用 32 位元組的資料):
下午 01:37:18
回覆自 8.8.8.8: 位元組=32 時間=7ms TTL=115
下午 01:37:19
回覆自 8.8.8.8: 位元組=32 時間=11ms TTL=115
下午 01:37:20
回覆自 8.8.8.8: 位元組=32 時間=10ms TTL=115
下午 01:37:21
回覆自 8.8.8.8: 位元組=32 時間=9ms TTL=115
下午 01:37:21

下午 01:37:21
8.8.8.8 的 Ping 統計資料:
下午 01:37:21
    封包: 已傳送 = 4,已收到 = 4, 已遺失 = 0 (0% 遺失),
下午 01:37:21
大約的來回時間 (毫秒):
下午 01:37:21
    最小值 = 7ms,最大值 = 11ms,平均 = 9ms

就會看到 ping 每一秒動作一次產生輸出後, 就會立刻經由 ForEach-Object 的腳本區塊處理, 所以中間的幾行訊息時間間隔都是 1 秒鐘。

兩者的差異可以參考這一篇文章

PowerShell 範例

以下蒐集一些小範例供參考使用。

以管理員權限執行指令

若要像是 Linux 下以 sudo 指令提升成管理員權限執行指令, 可以使用 Start-Process 搭配 -verb runas 參數執行指令, 例如:

❯ start-process netsh -verb runas

就會以管理員權限執行 netsh 指令, 過程中還是會詢問是否允許:

只要按即可。

也可以使用 runas 指令, 例如:

❯ runas /user:mee\shinwei netsh
輸入 mee\shinwei 的密碼:
嘗試以使用者 "mee\shinwei" 的身分啟動 netsh ...

計算執行時間

PowerShell 本身沒有計時的 cmdlet, 不過可以利用 .net 的 StopWatch 物件來計時, 首先產生物件:

❯ $sw = New-Object -TypeName System.Diagnostics.Stopwatch

接著即可使用 Start()、Stop()、Reset() 方法操控計時器, 並使用 Elapsed 屬性取得目前累計的計時時間。例如:

❯ $sw.Reset();$sw.Start();sleep 3;$sw.Stop()
❯ $sw.Elapsed

Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 3
Milliseconds      : 13
Ticks             : 30138681
TotalDays         : 3.48827326388889E-05
TotalHours        : 0.000837185583333333
TotalMinutes      : 0.050231135
TotalSeconds      : 3.0138681
TotalMilliseconds : 3013.8681

搜尋字串

Select-String

要篩選輸出內容, 可以使用 Select-String 搜尋文字或是規則表達式, 例如有一個這樣的文字檔:

type .\test.txt
hello
this
is
a
book

以下的指令就可以篩選出有 "this" 的那一行:

# cat test.txt | Select-String 'this'

this

要注意的是, Select-String 是針對文字物件搜尋, 如果是要篩選 cmdlet 輸出的物件, 必須先用 Out-string 轉成文字物件再篩選:

# alias | Out-String -stream | Select-String 'Get-Content'

Alias           cat -> Get-Content

Alias           gc -> Get-Content

Alias           type -> Get-Content

要特別留意 Out-String-stream 參數, 如果沒有加上此參數, 就會轉換成一個包含多行文字的字串物件, 這樣搜尋到字串時, 顯示的是多行的文字。加上 -stream 選項會轉換成多個單行文字的字串物件, 就只會顯示搜尋到文字的那一行。

find.exe

在 PowerShell 中如果要使用 find 搜尋檔案中的字串, 會因為 PowerShell 把雙引號括起來的內容當成字串物件, 使得實際傳送給 find 的參數內容沒有雙引號而出錯

使用 find 會出錯:

❯ find "this" test.txt
FIND: 參數格式不正確

必須用單引號把雙引號包起來, 改用 '"要搜尋的文字"' 才能將含有雙引號的文字當成參數傳給 find:

❯ find '"this"' test.txt

---------- TEST.TXT
this

findstr.exe

如果覺得太麻煩, 也可以改用 findstr 指令, 不需要引號:

❯ findstr this test.txt
this

找尋 gcc 特定表頭檔內的定義內容

以下是找尋 time.h 檔案中名稱含有 "time" 的自訂資料型別:

echo "" | gcc -E -xc -include 'time.h' - | Select-String -Pattern "typedef" | Select-String -Pattern "time" 

有關 gcc 選項說明如下:

  1. -E 會在前置處理完就停止。
  2. 最後指定 - 當檔名表示不是從檔案讀取原始碼, 而是從標準輸入讀取, 這裡因為有資料通道的轉向, 原始碼就是前面的 echo 送出的空字串。
  3. -x 是指定要編譯哪一種程式語言, 此處 -xc 表示是 C 語言。由於是從標準輸入讀取原始碼, 所以 gcc 無法依據副檔名判斷程式語言種類, 這裡一定要指定程式語言才能正常運作。
  4. -include 可以指定要匯入的表頭檔, 這裡就是將 time.h 匯入。

以上的 gcc 指令實際結果就是讓前置處理器把 time.h 讀入與空的原始碼合併後輸出, 也就可以看到 time.h 檔案的內容。

後面透過資料通道串接的 Select-String 可以搜尋文字。

這個方式找不到像是 #define 定義的項目, 因為在前置處理器處理後, 這些項目都已經被取代掉了。

計算資料夾大小

# Get-ChildItem .\get-pip.py | Measure-Object -Property Length -sum

Count             : 1
Average           :
Sum               : 2658865
Maximum           :
Minimum           :
StandardDeviation :
Property          : Length

更新 PowerShell Core 到最新版

要在 PowerShell 內更新 PowerShell, 可以使用以下指令

iex "& { $(irm https://aka.ms/install-powershell.ps1) } -UseMSI"
  • irm 是 Invoke-RestMethod 這個 cmdlet 的別名, 可以送出 http 請求, 用來取得結構性的資料。本例就是用它來下載 PowerShell 的安裝腳本
  • -UseMSI 是安裝腳本的選項, 表示下載 MSI 格式的安裝檔執行安裝程序。
  • iex 是 Invode-Expression 這個 cmdlet 的別名, 可以執行字串參數內的指令。本例就是執行後面參數字串中叫用腳本區塊的動作。

Module

以下指令可以得知模組的安裝位置:

> Get-Content Env:\PSModulePath
C:\Users\ShinWei\Documents\PowerShell\Modules;C:\Program Files\PowerShell\Modules;c:\program files\powershell\7\Modules;C:\Program Files\WindowsPowerShell\Modules;C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules

以下指令可以列出已載入的模組:

> Get-Module

ModuleType Version    PreRelease Name
---------- -------    ---------- ----
Manifest   1.0.0.0               DnsClient
Manifest   7.0.0.0               Microsoft.PowerShell.Management
Manifest   7.0.0.0               Microsoft.PowerShell.Security
Manifest   7.0.0.0               Microsoft.PowerShell.Utility
Manifest   7.0.0.0               Microsoft.WSMan.Management
Script     2.0.399               oh-my-posh
Script     1.0.0      beta4      posh-git
Script     2.1.0                 PSReadLine

搜尋網路上可安裝的模組:

> Find-Module posh-git

Version              Name                                Reposito
                                                         ry
-------              ----                                --------
0.7.3                posh-git                            PSGalle…

若要包含未正式發佈的版本:

> Find-Module posh-git -AllowPrerelease

Version              Name                                Reposito
                                                         ry
-------              ----                                --------
1.0.0-beta4          posh-git                            PSGalle…

已安裝的模組會在執行到其中的指令時自動載入, 例如若先卸載已載入的模組, 然後執行 oh-my-posh 模組的 Set-Theme 指令, 就會自動載入 oh-my-posh 模組:

> Get-Module | Remove-Module
> Get-Module

ModuleType Version    PreRelease Name
---------- -------    ---------- ----
Manifest   7.0.0.0               Microsoft.PowerShell.Management
Manifest   7.0.0.0               Microsoft.PowerShell.Utility
Script     1.4.7                 PackageManagement
> Set-Theme Agnoster
> Get-Module

ModuleType Version    PreRelease Name
---------- -------    ---------- ----
Manifest   7.0.0.0               Microsoft.PowerShell.Management
Manifest   7.0.0.0               Microsoft.PowerShell.Utility
Script     2.0.399               oh-my-posh
Script     1.4.7                 PackageManagement
Script     1.0.0      beta4      posh-git