Differences
This shows you the differences between the selected revisions of the page.
| library_example_recursive_search_text 2014-12-01 | library_example_recursive_search_text 2025-04-10 (current) | ||
| Line 1: | Line 1: | ||
| - | ===== Search recursively for text in remote directory / Grep files over SFTP/FTP protocol ===== | + | ====== Search recursively for text in remote directory / Grep files over SFTP/FTP protocol ====== |
| - | If you do not have your favorite language, use [[library_powershell|PowerShell]]. | + | The following script uses [[library|WinSCP .NET assembly]] from a [[library_powershell|PowerShell]] script. If you have another preferred language, you can easily translate it. |
| - | <code powershell> | + | The script is distributed in WinSCP installer as a [[extension|WinSCP extension]]. |
| - | function SearchDirectory ($session, $path, $wildcard, $text) | + | |
| + | To run the script manually use: | ||
| + | |||
| + | <code> | ||
| + | powershell.exe -File SearchText.ps1 -sessionUrl "sftp://username:password;fingerprint=ssh-rsa-xxxxxxxxxxx...@example.com/" -path "/path" -text "text" | ||
| + | </code> | ||
| + | |||
| + | See also [[library_example_listing_files_matching_wildcard|*]]. | ||
| + | |||
| + | You can alter the script for other tasks, instead of grepping the matching files. You can for example [[library_session_removefiles|remove]] or [[library_session_getfiles|download]] the matching files. Just modify the action in the ''Action on match'' block accordingly. | ||
| + | |||
| + | <code powershell - SearchText.ps1> | ||
| + | # @name &Search for Text... | ||
| + | # @command powershell.exe -ExecutionPolicy Bypass -File "%EXTENSION_PATH%" ^ | ||
| + | # -sessionUrl "!E" -path "!/" -text "%Text%" -wildcard "%Wildcard%" ^ | ||
| + | # -pause -sessionLogPath "%SessionLogPath%" | ||
| + | # @description Searches recursively for a text in the current remote directory | ||
| + | # @version 8 | ||
| + | # @homepage ~~SELF~~ | ||
| + | # @require WinSCP 5.16 | ||
| + | # @option Text -run textbox "Text:" | ||
| + | # @option Wildcard -run textbox "File mask:" "*.*" | ||
| + | # @option SessionLogPath -config sessionlogfile | ||
| + | # @optionspage ~~SELF~~#options | ||
| + | |||
| + | param ( | ||
| + | # Use Generate Session URL function to obtain a value for -sessionUrl parameter. | ||
| + | ····$sessionUrl = "sftp://user:mypassword;fingerprint=ssh-rsa-xxxxxxxxxxx...@example.com/", | ||
| + | [Parameter(Mandatory = $True)] | ||
| + | ····$path, | ||
| + | [Parameter(Mandatory = $True)] | ||
| + | $text, | ||
| + | ····$wildcard = "*.*", | ||
| + | ···$sessionLogPath = $Null, | ||
| + | [Switch] | ||
| + | $pause | ||
| + | ) | ||
| + | |||
| + | try | ||
| { | { | ||
| - | Write-Host ("Searching directory {0} ..." -f $path) | + | if (!$text) |
| + | ···{ | ||
| + | throw "No Text was specified." | ||
| + | ···} | ||
| - | $directoryInfo = $session.ListDirectory($path) | + | # Load WinSCP .NET assembly |
| + | ····$assemblyPath = if ($env:WINSCP_PATH) { $env:WINSCP_PATH } else { $PSScriptRoot } | ||
| + | Add-Type -Path (Join-Path $assemblyPath "WinSCPnet.dll") | ||
| - | foreach ($fileInfo in $directoryInfo.Files) | + | # Setup session options |
| + | ····$sessionOptions = New-Object WinSCP.SessionOptions | ||
| + | ····$sessionOptions.ParseUrl($sessionUrl) | ||
| + | |||
| + | $session = New-Object WinSCP.Session | ||
| + | |||
| + | try | ||
| { | { | ||
| - | $filePath = ($path + "/" + $fileInfo.Name)· | + | $session.SessionLogPath = $sessionLogPath |
| - | ········ | + | |
| - | if ($fileInfo.IsDirectory) | + | # Connect |
| + | $session.Open($sessionOptions) | ||
| + | |||
| + | # Recursivelly enumerate files to grep | ||
| + | ········$fileInfos = | ||
| + | ············$session.EnumerateRemoteFiles( | ||
| + | $path, $wildcard, [WinSCP.EnumerationOptions]::AllDirectories) | ||
| + | |||
| + | foreach ($fileInfo in $fileInfos) | ||
| { | { | ||
| - | # Skip references to current and parent directories | + | # Action on match |
| - | if (($fileInfo.Name -ne ".") -and | + | |
| - | ···············($fileInfo.Name -ne "..")) | + | # Modify the code below if you want to do another task with |
| + | # matching files, instead of grepping their contents | ||
| + | |||
| + | ···········if ($fileInfo.FileType -eq "L") | ||
| { | { | ||
| - | # Recurse into subdirectory | + | Write-Host "Skipping symlink $($fileInfo.FullName)..." |
| - | ················SearchDirectory $session $filePath $wildcard $text | + | |
| } | } | ||
| - | } | + | ············else |
| - | ········else | + | |
| - | { | + | |
| - | # Does file name match wildcard? | + | |
| - | if ($fileInfo.Name -Like $wildcard) | + | |
| { | { | ||
| - | Write-Host ("File {0} matches mask, searching contents..." -f $filePath) | + | Write-Host "File $($fileInfo.FullName) matches mask, searching contents..." |
| - | $tempPath = ($env:temp + "\" + $fileInfo.Name) | + | $tempPath = (Join-Path $env:temp $fileInfo.Name) |
| # Download file to temporary directory | # Download file to temporary directory | ||
| + | $filePath = [WinSCP.RemotePath]::EscapeFileMask($fileInfo.FullName) | ||
| $transferResult = $session.GetFiles($filePath, $tempPath) | $transferResult = $session.GetFiles($filePath, $tempPath) | ||
| - | # Did the download succeded? | + | # Did the download succeeded? |
| if (!$transferResult.IsSuccess) | if (!$transferResult.IsSuccess) | ||
| { | { | ||
| Line 43: | Line 99: | ||
| # Search and print lines containing "text". | # Search and print lines containing "text". | ||
| # Use -Pattern instead of -SimpleMatch for regex search | # Use -Pattern instead of -SimpleMatch for regex search | ||
| - | Select-String -Path $tempPath -SimpleMatch $text | + | $matchInfo = Select-String -Path $tempPath -SimpleMatch $text |
| + | # Print the results | ||
| + | foreach ($match in $matchInfo) | ||
| + | { | ||
| + | Write-Host "$($fileInfo.FullName):$($match.LineNumber):$($match.Line)" | ||
| + | } | ||
| # Delete temporary local copy | # Delete temporary local copy | ||
| Remove-Item $tempPath | Remove-Item $tempPath | ||
| Line 49: | Line 110: | ||
| } | } | ||
| } | } | ||
| - | } | ||
| - | } | ||
| - | |||
| - | try | ||
| - | { | ||
| - | # Load WinSCP .NET assembly | ||
| - | Add-Type -Path "WinSCPnet.dll" | ||
| - | |||
| - | # Setup session options | ||
| - | $sessionOptions = New-Object WinSCP.SessionOptions | ||
| - | $sessionOptions.Protocol = [WinSCP.Protocol]::Sftp | ||
| - | $sessionOptions.HostName = "example.com" | ||
| - | $sessionOptions.UserName = "user" | ||
| - | $sessionOptions.Password = "mypassword" | ||
| - | $sessionOptions.SshHostKeyFingerprint = "ssh-rsa 2048 xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx" | ||
| - | |||
| - | $session = New-Object WinSCP.Session | ||
| - | |||
| - | $session.SessionLogPath = "session.log" | ||
| - | |||
| - | try | ||
| - | { | ||
| - | # Connect | ||
| - | $session.Open($sessionOptions) | ||
| - | |||
| - | $remotePath = "/home/user" | ||
| - | $wildcard = "*.txt" | ||
| - | $text = "blah" | ||
| - | |||
| - | # Start recursive search | ||
| - | SearchDirectory $session $remotePath $wildcard $text | ||
| - | |||
| } | } | ||
| finally | finally | ||
| Line 87: | Line 116: | ||
| $session.Dispose() | $session.Dispose() | ||
| } | } | ||
| - | · | + | |
| - | exit 0 | + | $result = 0 |
| } | } | ||
| - | catch [Exception] | + | catch |
| { | { | ||
| - | Write-Host $_.Exception.Message | + | Write-Host "Error: $($_.Exception.Message)" |
| - | exit 1 | + | $result = 1 |
| } | } | ||
| + | |||
| + | # Pause if -pause switch was used | ||
| + | if ($pause) | ||
| + | { | ||
| + | Write-Host "Press any key to exit..." | ||
| + | [System.Console]::ReadKey() | Out-Null | ||
| + | } | ||
| + | |||
| + | exit $result | ||
| </code> | </code> | ||
| + | |||
| + | ===== [[options]] Options ===== | ||
| + | |||
| + | &screenshotpict(extension_recursive_search_text) | ||
| + | |||
| + | In the //Text// box, specify the text to look for. The option is available when executing the extension only. | ||
| + | |||
| + | In the //File mask// box, specify a [[file_mask|file mask]] to select files. The option is available when executing the extension only. | ||
| + | |||
| + | In the //Session log file//, you can specify a path to a [[logging|session log file]]. The option is available on the [[ui_pref_commands|Preferences dialog]] only. | ||
| + | |||
| + | In the //Keyboard shortcut//, you can specify a [[custom_key_shortcuts|keyboard shortcut]] for the extension. The option is available on the [[ui_pref_commands|Preferences dialog]] only. | ||