三键成码:在 Powershell 中用三个字母和任意符号输出 Hello World

日期:
分类: Selected 5
标签: PowerShell 1 编程 1

其實 這篇文章 在去年暑假就已經寫好,不過最近我的 pull request 才被合併,已經快忘掉了。現修繕後發出來。

規則

三鍵成碼一項程式設計比賽,要求參賽者僅用三個字母或數字以及任意數量的符號編碼輸出 Hello, World!,並且檔案體積最小者獲勝。以下是詳細規則:

  1. 參賽選手自行挑選三個「字母 / 數字」來編寫程式碼,程式碼中的「字母 / 數字」只能且必須 有這三個(大小寫算兩個不同的鍵)
  2. 除此三個「字母 / 數字」外,額外允許使用普通鍵盤上的所有 ASCII 符號(例如 {, ; 等)
  3. 該程式執行後輸出 Hello, World!, 有且僅有該內容,要求分毫不差,注意大小寫和符號
  4. 上傳的所有檔案體積最小者獲勝!(結果截圖、說明文件等的不算)
  5. 不限程式語言,但禁止使用極小眾語言;我們對極小眾的定義是:Github 語言統計 上從未出現過的語言
  6. 所有的依賴條件均需是在本比賽開始前就是已有的;否則請上傳,作為檔案內容統計的一部分!
  7. 要求使用平庸的編譯/執行命令、測試環境、檔名,除平庸部分外,其餘部分也需要符合上述 1, 2 兩條的要求
  8. 靜態型別語言的入口函式名,不受 1 中的限制。例如 C 語言的 int main, Java 的 public static void main

截至 2023 年 3 月,PowerShell 在 GitHub 使用量排第 32 名,佔比 0.102%,符合要求。1

程式碼

選擇的 3 個字母:iex

$i=$?+$?
$e=$i+$i
$xi=$x=$e+$i
$xe=""+$x+$x
$xx=""+$x+--$e
$x=(""+$?)[$i]
iex "`$ex=""``$x{$xe}"";`$xe=""``$x{$xx}"""
$ii=$e+++$i
$ix=$ii+$i
$ei=$e*$i
$x="``$x{"
iex "`$e=""$x$e$ei}e$x$xi$xe}$x$xi$xe}$x$xi$ex}, $x$ii$ix}$x$xi$ex}$x$ix$i}$x$xi$xe}$x$xi$e}!"""
$e

壓成一行時語句結尾需要有分號。

$i=$?+$?;$e=$i+$i;$xi=$x=$e+$i;$xe=""+$x+$x;$xx=""+$x+--$e;$x=(""+$?)[$i];iex "`$ex=""``$x{$xe}"";`$xe=""``$x{$xx}""";$ii=$e+++$i;$ix=$ii+$i;$ei=$e*$i;$x="``$x{";iex "`$e=""$x$e$ei}e$x$xi$xe}$x$xi$xe}$x$xi$ex}, $x$ii$ix}$x$xi$ex}$x$ix$i}$x$xi$xe}$x$xi$e}!""";$e

環境

PowerShell 7.2.5,需要自行安裝,Windows 自帶的版本較舊,沒有 Unicode 跳脫字元。

程式碼可以在終端直接貼上,或者透過 .\x.ps1 執行。

原理

選擇字母

讓我們先來看看其他提交者所用的語言中是怎麼做到的:

  • JavaScript 有 JSFuck 這種東西,甚至可以只有符號,自然是不消說的
  • Python 版本中選擇了 exec——Python 中的 eval 只能執行單純的表示式,無法建立變數,而且需要 4 個字母;而 exec 可以執行程式碼塊
  • Bash 用了 tr
  • Ruby 選了 c 和另外兩個數字——Ruby 中 << 是字串連線運算子,$>stdout,後面用格式化字串 "%c" 和一個整數陣列
  • C 利用了 UB 和不平凡的編譯命令
  • PHP 選了 Hel,剩下的字元驚為天人,不知道是利用了什麼隱式型別轉換,看不懂

PowerShell 中無法對 Char 進行加減操作,顯式地進行型別轉換也需要類似 [char]65 至少 4 個字母。2 所以只能考慮 eval。PowerShell 中的命令名都很長,不過 Invoke-Expression 有別名 iex。另外有一個可以執行字串的運算子 &,但是隻能是命令名,不能帶引數。3

第一個數字

因為不能直接出現數字了,所以需要想辦法得到第一個 Int 型別的值。發現陣列下標為空字串 '' 時可以得到陣列的第一個元素,但是如果想要後面的字母的話,PowerShell 並沒有提供 pop 等函式。4 數字字面量也都至少需要有 0 出現。所以只能透過其他型別隱式轉換出 Int 來。

$i = $? + $?
  • $? 表示上一條命令的返回值,在初始時和上一條命令沒有出錯時為 True5
  • PowerShell 中的 ==-eq,需要額外的字母。
  • 當 Bool 型別轉換為整數時,True1,故 $i26

剩下的大部分操作都是在構造其他數字,程式碼的長度應該可以再壓縮一點。

構造 Unicode

$x = ("" + $?)[$i]
  • + 運算子的兩個引數型別不一致的時候,會將第二個引數隱式轉換為第一個引數的型別。故此處 $e 為 String "True"

至此,我們有了所需的數字和字母 u,可以用 "u{x}" 來生成一切字元了。不過,"Hello World!" 除了可以直接輸入的 e、空格和 !,十六進位制值中還需要 cf。幸好這兩個的編碼分別為 6366,沒有 a-f 出現。$e 實際上就是:

$e = "``u{48}e``u{6c}``u{6c}``u{6f}, ``u{57}``u{6f}``u{72}``u{6c}``u{64}!";

跳脫字元

iex "`$ex=""``$x{$xe}"";`$xe=""``$x{$xx}"""
  • 字串用雙引號的好處是可以直接嵌入變數。如果要 escape 的話,需要在 $"` 前面加上 `" 也可以自身重複兩次 "" 來 escape。7

列印值

PowerShell 預設列印出前一個表示式的值,所以不需要拼湊出 "echo"iex 什麼的。

其他可能的方法

  • 變數賦值時可以直接是命令的輸出。比如舊版 PowerShell 可以透過 $ls = ls; $ls[x][x] 拿到 ls 命令表頭具體的一個 Char,或許有的命令的輸出包含全部所需的字母。但是新版中大部分命令的輸出不再是字串而是物件了,兩次下標拿到的仍然和原結果一樣。
  • $error 是一個存放了錯誤資訊字串的陣列。8 不過,錯誤會直接輸出,不符合「該程式執行後輸出 Hello, World!, 有且僅有該內容,要求分毫不差」的要求。

---

  1. 點選第 5 條規則中的連結檢視。 ↩

  2. Converting ASCII and Characters - Power Tips - Power Tips - IDERA Community ↩

  3. Why does invoke operator (& and Invoke-Expression produce different results for the same input? - Stack Overflow) ↩

  4. PowerShell Remove item 0 from an array - Stack Overflow ↩

  5. $? - PowerShell Core - About - Automatic Variables ↩

  6. Conversion#Conversion to integer ↩

  7. Escaping in PowerShell ↩

  8. What is use of $error variable in PowerShell? ↩

版权许可

  1. 本作品 采用 知识共享 署名—非商业性使用 4.0 国际许可协议CC BY-NC 4.0 International)许可,阁下可自由地共享(复制、发行) 和演绎(修改、转换或二次创作) 这一作品,唯须遵守许可协议条款。

评论

评论将在审核后显示,阁下可以在本博客的 Github 仓库的 拉取请求列表 中查看。提交成功后会自动跳转。

本站不支持 Dark Reader 的暗色模式,请对本站关闭后再访问,亮色模式的对比度、亮度等选项不受影响。部分页面右上角提供暗色模式切换按钮,如果你没看到,说明你的浏览器尚不支持此特性。本提示不依赖于 JavaScript,你可自行查找其他用户在本站发表的关于如何关闭此提示的评论。