只需花 5分鐘就能阻擋 阻擋 AdBlock 網站的阻擋元件
話說我最近在 Facebook 上看到這一篇很有趣的文章,講述如何檢查出 Adblock 然後對該使用者彈出視窗來禁止他繼續閱讀: https://blog.reh.tw/archives/2273 大概會彈出這樣的視窗 先不論這方法是不是真的會讓讀者留久一點還是氣得直接按右上角的叉叉,對我這種習慣了網頁開發的野生技術程序員來說這簡直就是像彈出一張「戰帖」一樣。 好,我就來應戰吧! 對於要解決類似這種的 Adblock Blocker 其實很簡單,你只需要把阻擋你的東西移除掉,把 body 的 css 改一點點(通常是移除 overflow: hidden)就能夠繼續看的了。所以首先,我們要把找到擋著我們的東西。 我這裡使用的是 Firefox,你只要對擋著你的東西按右鍵 --> Inspect Element 之後到程式碼裡面找到代表這個部分的 div 的 id 或 class name 然後在 GreezeMonkey (Firefox 插件)開一個新的 script,引用 jQuery 跟 include 你需要阻擋 阻擋 Adblock 的網站,然後把全部相關的 id 跟 class 都 remove 掉就可以了。以下是一個非常簡單的例子: // ==UserScript== // @name REH TW Block AD Blocker // @version 1 // @grant none // @include https://blog.reh.tw/* // @require https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js // ==/UserScript== console.log("Removing Adblocker"); $("body").removeClass("modal-open"); $(document).find(".modal-backdrop fade show").remove(); $("#adBlockMsg").remove(); 然後我們重新載入網頁: 耶!阻擋 Adblock 的東西被我們阻擋了!
同步上載與 RAM 不足的問題
在 Raspberry Pi 上面執行類似 arozos 之類的大型系統不免會出現一個問題,就是當要處理大量 IO 的時候總會卡住(畢竟最快的儲存器( RAM、記憶體) 只有那 2 - 4GB,雖然說現在已經出到 8GB 了,但是那個價格我倒不如買台二手的 ThinkCentre M73?),所以要如何在有限 RAM 的情況下讓檔案上下載速度最佳化是其中一個很有趣的研究方向。 上載的方式 Single-thread Upload (單執行緒上載) 我們就先由正常的上載情況說起吧,我在這裡說的就是一般的 HTML5 Form 的檔案上載。每次一個檔案,很簡單吧?可是這速度實在太慢了,例如 Google Drive 之類的很久之前就已經用了多執行緒上載了,所以要一個一個檔案上載的話,速度是無法接受的。 Multi-thread Upload (多執行緒上載) 就是如此,我在這文章裡說的主要是多執行緒上載模式。簡單來說 Multi-thread Upload 就是由 Browser 對伺服器的上載接點打開多個 request,並同時向接點上載資料的方式。 那 Multi-thread Upload 又有甚麼問題? Multi-thread Upload 雖然能夠縮短使用者上載檔案的時間,能夠盡用 bandwidth,但是伺服器也要處理短時間內同時上載的檔案,導致系統的開發有點考功夫,有時候甚至要考慮 Race Condition 問題。 在以前純 PHP 5 + Apache 2的年代,伺服器每次只能夠同時處理一個要求,因此很少會出現資源不足的這個問題。但是到現在,很多人都轉用更先進的語言來開發網頁伺服器了,自然也需要處理到資源分配的問題。以下我就以 Go 作為「思考語言」來分析一下吧。 先 Buffer (緩存)到 RAM 不就行了嗎? Buffering 是一個常用來處理 IO 的方法,由於下載的時候就是由作業系統跟硬碟的緩存機制處理,所以我們只需要處理上載就好。我們就先假設系統採用以下的一個邏輯處理使用者上載的檔案 //建立一個 25MB 的 Buffer r.ParseMultipartForm(25 << 20) //從 POST 中取得 file file, handler, _ := r.FormFile("file") //建立儲存的檔案 dest, _ := os.Create(handler.Filename) //從 File 把內容複制到 dest if _, err := io.Copy(dest, file); err != nil { panic(err) } //關閉打開了的 file descriptor (通稱 fd,即檔案描述符) file.Close() dest.Close() 你那看到,在檔案上載到伺服器的一刻,伺服器已經分配了 25MB 的記憶體給這個 request 了。而這個 request 會把這空間卡住,直到 file.Close() 之前都不會把記憶體還給作業系統,因此每上載一個檔案 = 吃掉一個不少於檔案大小的記憶空間。而在 file.Close() 之前還有一個 blocking 的 io.Copy() 把上載的資料複制到最終的儲存位置(例如硬碟),所以最後就是要等硬碟寫完才能把記憶體釋放出來。 簡單來說就是這樣 所以,這東西很慢! 為了解決慢的問題,我們來做多執行緒的檔案搬運吧!Go-routine 是一個很神奇的東西,不需要幾行我們就可以寫出支援 Multi-threading 的程式,只要我們把 io.Copy 的部分改成: //建立儲存的檔案 dest, _ := os.Create(handler.Filename) //把複制的部分放到 Go routine,讓客戶端繼續上載下一個檔案 go func(dest *os.File, file *os.File) //從 File 把內容複制到 dest if _, err := io.Copy(dest, file); err != nil { panic(err) } //關閉打開了的 file descriptor (通稱 fd,即檔案描述符) file.Close() dest.Close() }(dest, file) 新的方法看起來像這樣,比起上面的方法快多了 對,這樣檔案上載的速度快多了,而且還能同時發起幾個上載的要求,但是這還在檔案比較小,RAM 裝得下的情況下都還好,但是檔案大的時候該怎麼辦? 例如說,在只有 2GB…
Golang os/exec 使用方法(筆記)
此筆記是一個簡單純粹的快速參考目錄,方便我(或是你?)在開發時使用 os/exec 之用。如需要詳細的教學或 API 資料,請直接參閱 https://golang.org/pkg/os/exec/ 此文中有部分例子參考自: https://colobu.com/2017/06/19/advanced-command-execution-in-Go-with-os-exec/ 執行指令並回傳結果 func main() { cmd := exec.Command("ls", "-lah") out, err := cmd.CombinedOutput() if err != nil { log.Fatalf("cmd.Run() failed with %s\n", err) } fmt.Printf("combined out:\n%s\n", string(out)) } 加入額外的環境參數再執行指令 cmd := exec.Command("programToExecute") additionalEnv := "FOO=bar" newEnv := append(os.Environ(), additionalEnv)) cmd.Env = newEnv out, err := cmd.CombinedOutput() if err != nil { log.Fatalf("cmd.Run() failed with %s\n", err) } fmt.Printf("%s", out) 指定 CWD 並執行指令 cmd:= exec.Command("git", "log") cmd.Dir = "your/intended/working/directory" out, err := cmd.Output() 注意:使用上術的方式時請確保相關的指令存在於 ENV 參數內(或 Windows 的 %PATH%),如果不是,請使用 binary 的 Absolute Path,例如: abspath, _ := filepath.Abs("./mybinary") cmd:= exec.Command(abspath, "log") cmd.Dir = "your/intended/working/directory" out, err := cmd.Output() 在 Linux 下使用 os/exec + Pipe 或 Bash 運算式 rcmd := `iw dev | awk '$1=="Interface"{print $2}'` cmd := exec.Command("bash", "-c", rcmd) out, err := cmd.CombinedOutput() if err != nil { log.Println(err.Error()) } log.Println(string(out)) 在 Windows 下使用 os/exec + Batch 運算式 cmd := exec.Command("cmd", "/c", "ffmpeg -i myfile.mp4 myfile.mp3 && del myfile.mp4") out, err := cmd.CombinedOutput() if err != nil { log.Println(err.Error()) } log.Println(string(out)) 使用 String Slice 為指令傳進 os/exec args := []string{"hello", "world"} cmd := exec.Command("echo", args...)…
Raspberry Pi 的 SD 卡死掉了?有試過 fdisk 嗎?
我因為手頭上有很多台 Raspberry Pi,有時候不小心無故斷電了,Raspberry Pi Boot 不起來而且 SD 卡又寫不進,總會覺得是不是 SD 卡壞掉了? 其實不然,很多時候都只是 Partition Table 出了錯誤,雖然在 Windows 上會不停彈出 "無法格式化" 的錯誤警告,又或者甚至連 Disk Manager 都無法改更磁碟編號,但是卡本身還是可能有救的,最簡單的方法就是拿一台能正常運作的 Raspberry Pi 來跑 fdisk 了 檢查卡本身有沒有物理故障 要檢查這個很簡單,把 SD 卡使用 SD 轉 USB 轉接器插進 Raspberry Pi 的 USB 口前,執行一次下面的指令,插進了後再執行一次,看看有哪個新的儲存裝置彈出來了 ls /dev | grep sd 原本的儲存裝置列表 新的裝置列表,可見 /dev/sdf 出現了 如果你看到 /dev/sdx 出現了的話,恭喜你,你的卡沒壞。 重設 Parition Table 要重設 partition table,你就會需要用到 fdisk 功能。在這例子中我會使用 /dev/sdf 作為例子,你需要把裝置路徑更改到你的 SD 卡的儲存裝置編號。 首先,使用 sudo 或 su 模式啟動 fdisk sudo fdisk /dev/sdf 然後輸入 p 拍 enter,你會看到現在存在在卡上的 partition。輸入 d + enter ,重複直至所有 partition 被刪除。 輸入 n + enter 創建一個新的 partition,其餘的問題全部用預設值就好 之後再按 p + Enter,你會看到新的 Linux partition 已經準備好了。這個時候,你要把 Partition Type 設成 Windows 能讀取的格式,輸入 t + enter 之後輸入 L + enter,找到 Microsoft Basic Data 的代號 (在下面的例子是 11 號),輸入 11 + enter。 找到 Microsoft Basic data 的 partition type 完成之後你的 SD 卡 Partition table 應該會長這樣 看到 Sd 卡容量跟 Type = Microsoft Basic Data 最後就是輸入 w + enter 把更改真正的寫進 SD 卡內就大功告成了! Windows 格式化 把 SD 卡重新插到 Windows 主機上, Windows 會彈出提示要求把 SD 卡進行格式化。你最後只需要按照一般 Windows 格式化的方法就可以把 SD 卡救回來了 最後被救回的 SD 卡
由網頁拖放檔案到桌面的神奇方法
你有沒有想過到底怎樣可以把檔案由 DOM ELEMENT 拖放下載到桌面? 由本地的檔案管理員或是桌面拖放檔案到網頁上的話大家應該都很容易在一些網站如 facebook 或 youtube 看到可是說由網頁端拖放檔案到桌面就更少見了。 為甚麼我從來都沒看過有網站用這個方法讓使用者下載檔案? 這當然是有原因的,因為這個功能只有 Google Chrome 支援而已。對於不能跨平台用的 API 沒人用那當然很正常吧?但是作為研究最新技術的 imuslab,這個有趣的功能當然要加進去 ArOZ Online 系統吧?但是,要使用這個檔案由網頁拖放到桌面卻是有這麼堆特別要求: 拖放的元件必須是連接 (<a>)元件的 href 一定要填寫不能留空,但是也可以填 javascript:void(0); draggable 需要設為 true不能使用DOM 屬性 ondragstart ,需要使用 JavaScript 來 加 EventListener 如果你的情況符合上面的條件,你就可以把檔案透過這個 <a> Element 拖放到本地檔案系統了。以下為一個簡單的 Example: $("#element")[0].addEventListener("dragstart",function(evt){ var targetMime = "plain/text"; $.ajax({ url: "media/getMime/?file=" + encodeURIComponent(properties.filepath), success: function(data){ if (data.error !== undefined){ targetMime = "text/directory"; }else{ targetMime = data; } }, async: false }); evt.dataTransfer.setData("DownloadURL",targetMime+":"+ properties.filename +":"+ "http://" + location.hostname + ":" + location.port + "/media/?file=" + encodeURIComponent(properties.filepath)); },true); 到這裡有幾點值得注意 MIME Type 必須符合檔案的內容,不然拖放之後會出現 Internet Shortcut 檔案而不是檔案本身如果要在 event 裡使用 AJAX,那 Request 必須為 Synchronize ,就好像上面的例子中用來要求取得檔案 MIME Type 的 AJAX request 一樣當設定 DownloadURL 參數時時必需使用完整 URL 而不能使用 Relative PathaddEventListener 的 Option 值必須為 true 就是這樣,你就能成功的做出一個可以拖放到桌面的 HTML5 DOM Element 了喔! 19/6/2020 補充 如果你在下載的時候遇到這個問題 失敗 已封鎖的例子 一般是因為以下兩個原因 你寫入的 URL 不正確或檔案不存在上面的例子裡的一個小 Bug (Hard code 了 http 的問題) 對於非 http 的使用者來說,你可以把這一行 evt.dataTransfer.setData("DownloadURL",targetMime+":"+ properties.filename +":"+ "http://" + location.hostname + ":" + location.port + "/media/?file=" + encodeURIComponent(properties.filepath)); 改成 evt.dataTransfer.setData("DownloadURL",targetMime+":"+ properties.filename +":"+ location.protocol + "//" + location.hostname + ":" + location.port + "/media/?file=" + encodeURIComponent(properties.filepath)); (注意多出來的 location.protocol)來自動 Detect 目前是使用 http: 還是 https: 協議
高自由度的 Web Desktop 服務?
說到高自由度的網上平台,你會想起哪一些網頁 / 平台? Google Drive? Facebook? Youtube? 還是說可以 post 奇怪照片的網站 Twitter(?) 平台自由度與用途 一般網頁都不會給予用家太高的自由度,這理由很簡單,一個網頁就是為了提供一種服務,無論那種服務所覆蓋的範圍是廣還是窄,以下是一些例子: draw.io → 網上圖表制作easyEDA → 網 上 PCB 制作與設計onlinesequencer → 網 上音樂制作facebook / twitter → 網 上社交分享Youtube / viemo / po***** → 網 上影片分享平台Office 365 / Google Document → 網上辦公軟件 所以,以商業角度來說,每一個平台必須要有他們的獨立功能才能夠有辦法營運,然而這亦會導致一些問題,就是平台依賴性 (Platform dependencies) 跟 供應商鎖定 (Vendor Lockdown) 問題。 為甚麼我應該擔心這些問題? 由於這篇文章不是說關於這些 WebApps 的問題,所以我也不詳細解釋這些問題了。簡單來說就是: 如果 A 網頁系統用的格式跟 B 網頁系統用的格式不一樣,你是沒辦法把檔案移動到另一平台上面如果提供 A 網頁系統的公司倒閉了,那你使用 A 系統編寫的檔案全部都再用不了 要解決這個問題,除了使用開放格式 / 標準格式(如 IEEE 制定的格式)外,就是開源系統本身了。但是, 真的這麼不幸擁有 A 系統的公司倒了,我想應該不太多人會懂得用開源的系統重建一套新系統出來吧?比較有可能的是會有人開發一個轉接器把 A 系統的格式轉成另一家 B 系統,但是這個也不是這麼容易可以做到,特別對於重度依賴 A系統並有大量 A 系統格式的檔案的人來說。有甚麼可能更槽糕?就是連檔案本身也是儲存在 A 系統裡面的,當 A 系統倒的時候檔案就跟著系統消失了。 以本地運作為前提設計的 WebApp 所以另一個想法,也就是 ArOZ Online 裡面一直在用的系統,就是把 WebApp 的擁有權取回來的方法。由用者擁有 WebApp 而不是由網頁提供者所擁有。這樣,就算供應該服務的公司倒了,WebApp 依舊能用,檔案也不會消失。例如好像說能在主機啟動的時候檢查更新,並把 WebApp 模組下載到啟動資料夾之類的。當然,你也可能會問: 這不就是 React Native 了嗎? 不對喔! (´・ω・`) 這裡就是 ArOZ Online 設計的不同了。React Native 針對的是本地的作業系統,例如 iOS,Windows 或是 MacOS,不同的作業系統會有不同的安裝檔,裡面各包一個 Chromium。那既然是這樣,那為甚麼不直接讓用家在 Chromium ( 或 Firefox / Chrome /Edge 之類的瀏覽器)打開你的 WebApp 呢?這樣不但省空間還能直接用標準 Web Standard 來開發模組。 而這個就是 ArOZ Online 的設計理念:屬於自己的 WebApp 伺服器;而且由於是使用 Web Protocol 傳送的,所以也跟其他 WebApp 一樣去到哪裡,甚至不需要是自己的電腦,都可以透過網絡存取到 WebApp 的內容。 論高自由度平台的條件 然而,因為這種設計已經脫離了一般 Web 開發的範圍,所以要開發一個這麼高自由度,多用途的系統會產生一大堆的問題,例如說 要重新設計 WebApp要想辦法解決本地檔案儲存問題怎樣以人性化的方式尋找及啟動 WebApp (aka 代替 Google 找 WebApp 的過程)怎樣讓使用者不用學習也能適應使用這套系統? 嗯?你有沒有覺得這些要求跟你平常在用的東西很像?沒錯,那就是作業系統 (Operating System),而作業系統本身也的確是一個很高自由度的東西,你可以把它用作 Media Center,可以用來辦公,遊戲,開發等等的用途。 我們需要的是一個能夠在網頁上運作的作業系統! 這個也是我開發 ArOZ Online 的理由:把自己需要的服務放到 Web Desktop 上,那去到世界各地,只要有網絡連接都能用自己的系統和存取到自己主機上的檔案。這跟 NAS 系統很像,只不過不同的地方在於這系統不單單用來放檔案,它亦能用來做其他事情。 這說起來簡單,做起來卻很複雜。這是因為自由度太高會導致很想意想不到的錯誤。好像說只是要處理使用者上載的檔案名稱就已經是一個大麻煩了。由於要跟其他系統兼容,所以不能隨便把檔案改名然後把原檔名放進資料庫這種 wordpress 般的操作;也不能讓使用者隨便把 ../ 放進去檔案路徑裡,就結果而言就是很多很多的麻煩。(有興趣的話可以參考這篇文章) 但是,經過了 Beta + 1.0 preview 差不多 5 年的開發和測試,最終於出來的效果也超乎了我的想像:一個用起來跟本地作業系統一樣的高自由度網頁桌面系統! 很有 Ubuntu…
使用 Golang 動態載入插件 / 模組的方法
Golang 跟 PHP 或 Nodejs 不一樣,是一種需要預先 compile 的語言,相信這個大家都知道了。然而,這有一個很大的問題,就是無法像 php 那樣動態載入插件 / 模組。 那如果我想在已經 Compile 好的 Golang 程式上加上額外的功能應該怎樣做? 一開始我就為這問題煩惱了很久,然而最後我發現了一個不錯的解決方法,就是使用 Reverse Proxy。舉個例子,如果你能夠設定動態的 Reverse Proxy ,然後把一個新的模組指向一個新的 subdirectory ,那樣看上去不就跟新增了功能很相近了嗎? 所以,就在新的 ArOZ Online Core 裡面加入了這樣的一個結構,這裡我把它稱為 Subservice: Subservice 的概念很簡單,就是把 Reverse Proxy 的 process 拼進去跟 Core 共用同一個 STDIN 跟 STDOUT,然後網頁的部分就是用 Reverse Proxy 來處理,看上去所有模組都是由同一個程式所執行似的(實際上卻不是),它的運作邏輯大約是這樣: 掃描所有存在的資料夾,看看裡面有沒有可執行檔案使用 cmd.exec 加上 -info argument 啟動,等待回傳 JSON 格式的啟動設定把 working directory 轉換到資料夾內,並以 cmd.exec -port {指派的端口} 執行該程式把程式的 STDERR 跟 STDOUT 都指向 parent 的 STDOUT把服務要求的 Reverse Proxy 終端指向到該程式在用家存取該終端的時候進行 URL rewrite【在 Reverse Proxy 下使用模組】在parent 遇上 SIGKILL 的時候把 SIGKILL 都傳送到子服務並關掉程式 問題:可是這樣就存取不了伺服器的 DB 跟 File System API 對,所以這方法後來改成了只讓非兼容 ArOZ Online 的模組使用(例如 Aria2)。對於真的有需要存取系統核心部分 API 的模組來說,這個方法無法處理到 Database 存取跟 File System Virtualization 的部分,如果把 系統核心 API 都用 RESTful 的方式開出來錄又會有另一些安全問題,所以得想另一個方法去處理這個部分。 欸,那我內嵌一套 JavaScript Interpreter 不就可以了嗎? AJGI 架構 沒錯,所以後來就使用了一個新的架構,暫名 AJGI。這是一個可以讓系統執行 Javascript 的方法,同時讓 JavaScript 存取到系統內部的 function,可謂一舉兩得,至於效率嘛,既然你都是在寫插件就不要介意這麼多了。簡單來說邏輯是這樣的 由 front end 呼叫 AJGI ,並從檔案系統載入一段兼容的 JavaScript 代碼在 JavaScript 虛擬機中執行代碼透過 轉接器 使用 JavaScript 存取 ArOZ Online Core Golang 部分的核心功能(如需要)回傳資料到 frontend結束虛擬機 至於功能和限制之類的就要等之後的開發再進行測試才知道了
threejs 局部縮放變形
目標效果 在 Threejs 中如果你要對一個已經 extrude 的 geometry 進行變型的話,由於缺少內部的功能支援,基本上你就只能在 geometry 上手動的加上一個 Matrix4 變形。對於作我一樣沒學過四次元 Matrix 的軟件工程師來說看到這 4D matrix 就跟看到外星語言一樣。 When in doubt: use Brute Force 作為軟件工程師,當你面對一些無法解決的難題的時候一般只有兩種解決方法: Brute Force Algorithm Neural Network 所以我就理所當然的把所有 Matrix4 裡面有的洞洞都試填一次,最後得出的結論上: 把 Matrix4 第四行第三格填入你想要的縮放比例即可 效果 Matrix4 的結構 原碼大約長這樣: var geometry = new ExtrudeBufferGeometry( shape, data ); geometry.center(); var matrix = new Matrix4(); matrix.set( 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0.1, 1); geometry.applyMatrix( matrix ); updateGroupGeometry( mesh, geometry ); 所以說真的是 Brute Force 大法好!
Golang Header 中的 UTF-8 檔名
一般來說如果你想讓 Golang 去 Serve 一個 File 你會在檔頭加入以下兩行: w.Header().Set("Content-Disposition", "attachment; filename=" + filepath.Base(realFilepath)) w.Header().Set("Content-Type", r.Header.Get("Content-Type")) 這樣瀏覽器就不會播放多媒體檔案而是轉為下載模式。然而,對於一些含有特別字元 / 空格的檔案名稱,一般瀏覽器並無法完整讀取整個檔名,例如: Chōcho - Authentic Symphony (Acoustic ver.).mp4 //檔案名稱下載時因為空格會被 Firefox 讀取成 Chōcho 而如果要解決這個問題,你可以使用 Golang 的 url.QueryEscape( ) 功能,把檔案名稱先轉換為 URL Safe 的名稱,但是這會出現另一個問題,例如上面的例子經過 QueryEscape 之後會變成這樣 Chōcho+-+Authentic+Symphony+(Acoustic+ver.).mp4 這是因為雖然標準裡面有寫到 URL Encode + 跟 " "(空格)是相等的,但是很多主流瀏覽器對 URL Encode 的解讀只有 %20 而已,所以要解決這問題你就只能夠再把 + 替換成 %20 ,完整的代碼如下: w.Header().Set("Content-Disposition", "attachment; filename*=UTF-8''" + strings.ReplaceAll(url.QueryEscape(filepath.Base(realFilepath)),"+","%20")) w.Header().Set("Content-Type", r.Header.Get("Content-Type")) 那你可能會說你的 + 號不就是不見了嗎?嗯,沒錯,可是你有更好的解決方法嗎?
Golang 在 filepath.Glob 裡使用含方括號([ 跟 ])的路徑
由於 Golang 語言限制,filepath.Glob 裡面有一些字元是不能用的,例如說 [ 跟 ] 就是其中之一(還有 * 號,但是大部分作業系統都不會讓你把 * 號當作資料夾名稱,所以在這裡就不作處理了。一般來說,如果你要打開一個包含 [ ] 的路徑, filepath.Glob 會回傳空值。 path := "mymusic[mp3]/*" files, err := filepath.Glob(path); // files 會回傳空值 所以,很多工程師就幹脆不在資料夾名稱裡面使用 [ 跟 ],但是作為開發網頁版的檔案管理器,由於作為工程師是無法預計用戶的使用方式,我們必將要在 Glob 裡面支援 [ 跟 ],而方法可能比你想像中的簡單: realpath := "mymusic[mp3]" files, _ := filepath.Glob(realpath + "*") //這裡的 files 會是空值,寫在這裡是為了在下面省下一個 else 的 case if (strings.Contains(realpath, "[") == true || strings.Contains(realpath, "]") == true){ if (len(files) == 0){ //特別處理模式,以 * 號取代 [ 跟 ] newSearchPath := strings.ReplaceAll(realpath, "[","*") newSearchPath = strings.ReplaceAll(newSearchPath, "]","*") //掃描與輸入路徑相近的路徑名稱 tmpFilelist, _ := filepath.Glob(newSearchPath + "*") //在每個相近路徑中,找出擁有正確路徑的檔案 for _, file := range tmpFilelist{ file = filepath.ToSlash(file) if strings.Contains(file, realpath){ files = append(files, file) } } } } //正確回傳 log.Println(files)
目前第 1 頁,共有 6 頁