Is it possible to rename all the files in a folder with a simple program using vb.NET
Lets say there is a folder containing the files, files are vary every time loaded:
A123.txt
B456.txt
C567.txt
Will it be possible to rename these files in one operation,as below:
A_1.txt
B_2.txt
B_3.txt
This could be simplified but here is the long version with comments every step of the way
Imports System.IO
' get your filenames to be renamed
Dim filenames = Directory.GetFiles("c:\path\with\files")
' order them by whatever rule you choose (here it is by modification date)
Dim orderedFilenames = filenames.OrderBy(Function(s) File.GetLastWriteTime(s))
' rename the files into a dictionary<oldname, newname>
' will result in filename X_Y.Z where X is first letter, Y is order index, Z is extension
Dim newNameSelector =
Function(s As String, i As Integer)
Return New KeyValuePair(Of String, String)(s,
Path.Combine(Path.GetDirectoryName(s), $"{Path.GetFileName(s)(0)}_{i}{Path.GetExtension(s)}"))
End Function
' get new names using the newNameSelector function
Dim newNames = orderedFilenames.Select(newNameSelector)
' define the operation to rename the file using a keyvaluepair<oldname, newname>
Dim renameMethod = Sub(kvp As KeyValuePair(Of String, String)) File.Move(kvp.Key, kvp.Value)
' either sequential rename ...
For Each newName In newNames
renameMethod(newName)
Next
' ... or it could be multi threaded
Parallel.ForEach(newNames, renameOperation)
Notes:
File.Exists check is unnecessary based on the rename rule using the file index. If you change the rule it may be necessary. The rule is ambiguous in your question body. This assumes it's only run once.
Dictionary keys will be unique since Windows won't have duplicate filenames
To answer your question, Will it be possible to rename these files in one operation[?], no, but we can do it in one "line", with multiple threads, even. Just modify the path string "c:\path\with\files" and run this code
Parallel.ForEach(Directory.GetFiles("c:\path\with\files").OrderBy(Function(s) File.GetLastWriteTime(s)).Select(Function(s, i) New KeyValuePair(Of String, String)(s, Path.Combine(Path.GetDirectoryName(s), $"{Path.GetFileName(s)(0)}_{i}{Path.GetExtension(s)}"))), Sub(kvp) File.Move(kvp.Key, kvp.Value))
I think you may need to work out your renaming logic. That is why I had separated it in the long code sample. Just work on the logic inside newNameSelector.
If you want to order by name, simply
Dim orderedFilenames = filenames.OrderBy(Function(s) s)
This will remove "_20190607" from your filename
Dim newNameSelector =
Function(s As String)
Return New KeyValuePair(Of String, String)(s,
Path.Combine(Path.GetDirectoryName(s), String.Format("{0}{1}", Path.GetFileName(s).Split("_"c)(0), Path.GetExtension(s))))
End Function
I know this answer is getting hughmungus, but OP keeps providing new info, which means the question is morphing, thus so is the answer. According to his most recent comment, only the earliest of each prefix should be renamed.
' group the filenames into groups using key: "prefix_"
Dim groupedFilenames = Directory.GetFiles("c:\path\with\files").GroupBy(Function(s) Path.GetFileName(s).Split("_"c)(0))
Dim filenames As New List(Of String)()
' add the min from each group, using the name as the comparer
For Each g In groupedFilenames
filenames.Add(g.Min(Function(s) s))
Next
' rename the files into a dictionary<oldname, newname>
' will result in filename X_Y.Z where X is first letter, Y is order index, Z is extension
Dim newNameSelector =
Function(s As String)
Return New KeyValuePair(Of String, String)(s,
Path.Combine(Path.GetDirectoryName(s), String.Format("{0}{1}", Path.GetFileName(s).Split("_"c)(0), Path.GetExtension(s))))
End Function
' get new names using the newNameSelector function
Dim newNames = filenames.Select(newNameSelector)
' define the operation to rename the file using a keyvaluepair<oldname, newname>
Dim renameOperation = Sub(kvp As KeyValuePair(Of String, String)) File.Move(kvp.Key, kvp.Value)
' ... or it could be multi threaded
Parallel.ForEach(newNames, renameOperation)
Related
I'm trying get get only the last part of a file name in a server folder between the "_" and ".pdf" in order to delete specific files only.
The files in the invoices folder are like this: Invoice_12345.pdf, Invoice_2345.pdf, Invoice_5555.pdf, etc. I need to extract the "12345", "2345", and "5555", then I need to delete the file if it is not contained in a database query.
With the following code, I wish to extract the digits from the files in the folder, so how do I get the number between "_" and ".pdf"?
Dim files() As String = Directory.GetFiles(Server.MapPath("/Contents/Invoices/"))
For Each file As String In files
list.Add(file)
Next
Then I need to delete the file from the server if the extracted number is not contained in a dataset I query from the database, but maybe that is a question for a different post.
For example, for Invoice "5555"
If PaidFull = True
File.Delete(file)
End If
Of course, I would have to then get the full file name to delete if from the server at that point.
This is a way to get only the numbers :
For Each file As String In files
Dim the_number As String = Path.GetFileNameWithoutExtension(file)
the_number = the_number.Substring(the_number.LastIndexOf("_") + 1, the_number.Length - the_number.LastIndexOf("_") - 1)
list.Add(the_number )
Next
Or with Regex (ty to Jimi) :
dim the_number = Regex.Match(file, "_(\d+)\.").Groups(1).Value
You really don't need to mess with parsing the file name if you are sure the number won't pop up somewhere in the directory path.
'Dim list = Directory.GetFiles("C:\SomeDirectory").ToList
Dim myTestList As New List(Of String) From {"Invoice_12345.pdf", "Invoice_2345.pdf", "Invoice_5555.pdf"}
For Each fileName In myTestList
If fileName.Contains("5555") Then
Debug.Print(fileName) 'Or do what you need to do
End If
Next
I am running into an issue when i split a string on "_Pub" and get the back half of the string it removes the first character and I don't understand why or how to fix it unless i add the character back in
strFilePath = "/C:/Dev/Edge/_Publications/Ann Report/2013-2016/2016 Edge.pdf"
Dim relPath = strFilepath.Split("_Publications")(1)
lb.CommandArgument = relPath
returns Publications\Ann Report\2013-2016\2016 Edge.pdf
What you have as a delimiter is not a string array "string()" but a regular string. You need a string array to use a string as a delimiter. otherwise it takes the first char of your string.
https://msdn.microsoft.com/en-us/library/tabh47cf(v=vs.110).aspx
try this
Dim relPath = strFilepath.Split(new string() {"_Publications"}, StringSplitOptions.RemoveEmptyEntries)(1)
It appears that you want to get the part of the path starting at some directory. Splitting the path might not be such a good idea: imagine if there was a file "My_Publications_2017.pdf" in a directory "C:\Dev\Edge\_Publications". The split as you intended in the question would give the array of strings {"C:\Dev\Edge\", "\My", "_2017.pdf"}. As has been pointed out elsewhere, the String.Split you used doesn't do that anyway.
A more robust way would be to find where the starting directory's name is in the full path and get the substring of the path starting with it, e.g.:
Function GetRelativePath(fullPath As String, startingDirectory As String) As String
' Fix some errors in how the fullPath might be supplied:
Dim tidiedPath = Path.GetFullPath(fullPath.TrimStart("/".ToCharArray()))
Dim sep = Path.DirectorySeparatorChar
Dim pathRoot = sep & startingDirectory.Trim(New Char() {sep}) & sep
Dim i = tidiedPath.IndexOf(pathRoot)
If i < 0 Then
Throw New DirectoryNotFoundException($"Cannot find {pathRoot} in {fullPath}.")
End If
' There will be a DirectorySeparatorChar at the start - do not include it
Return tidiedPath.Substring(i + 1)
End Function
So,
Dim s = "/C:/Dev/Edge/_Publications/Ann Report/2013-2016/2016 Edge.pdf"
Console.WriteLine(GetRelativePath(s, "_Publications"))
Console.WriteLine(GetRelativePath(s, "\Ann Report"))
outputs:
_Publications\Ann Report\2013-2016\2016 Edge.pdf
Ann Report\2013-2016\2016 Edge.pdf
Guessing that you might have several malformed paths starting with a "/" and using "/" as the directory separator character instead of "\", I put some code in to mitigate those problems.
The Split() function is supposed to exclude the entire delimiter from the result. Could you re-check & confirm your input and output strings?
Background
Suppose I have a shiny app where the user can upload an Excel file. The users will have access to a certain Excel template and I want to make sure that only copies of this template are uploaded.
My current approach
My current approach is now as follows:
Check if sheet name xyz is present -> if not throw an error
Read data from sheet xyz, compare column names with requirements -> if missing columns throw an error
Repeat for all necessary sheets
Problem with the current approach
This requires a lot of hard coding required sheet names and required column names and becomes tedious.
Question
So my question: how can I assure that the user provides a valid file? What strategies do you usually use to make sure that the uploaded file can be properly processed by your apps?
Pseudo Code
library(shiny)
library(tidyverse)
ui <- fluidPage(fileInput("file", "Upload Excel"))
server <- function(input, output, session) {
observe({
req(input$file)
sheet1 <- tryCatch(read_xlsx(input$file$datapath, sheet = "xyz"),
error = function(e) {
## do some sort of error handling, e.g. write to a reactiveValue list
})
if (!all(.REQUIRED_FIELDS_FOR_XYZ %in% names(sheet1))) {
## signal error
}
})
}
If you are already using Excel, why not use a Macro to do the work for you. Consider listing file paths, checking format types, cell addresses, cell values, etc. The Macro below will do most of the heavy lifting for you.
Sub GetFolder_Data_Collection()
Dim colFiles As Collection, c As Range
Dim strPath As String, f, sht As Worksheet
Dim wbSrc As Workbook, wsSrc As Worksheet
Dim rw As Range
Dim sh As Worksheet, flg As Boolean
Set sht = ActiveSheet
strPath = ThisWorkbook.Path
Set colFiles = GetFileMatches(strPath, "*.xlsx", True)
With sht
.Range("A:I").ClearContents
.Range("A1").Resize(1, 5).Value = Array("Name", "Path", "Cell", "Value", "Numberformat")
Set rw = .Rows(2)
End With
For Each f In colFiles
Set wbSrc = Workbooks.Open(f)
Set wsSrc = wbSrc.Sheets(1)
For Each c In wsSrc.Range(wsSrc.Range("A1"), _
wsSrc.Cells(1, Columns.Count).End(xlToLeft)).Cells
rw.Cells(2).Value = wbSrc.Path
sht.Hyperlinks.Add Anchor:=rw.Cells(1), Address:=wbSrc.Path, TextToDisplay:=wbSrc.Name
rw.Cells(3).Value = c.Address(False, False)
rw.Cells(4).Value = c.Value
rw.Cells(5).Value = c.NumberFormat
i = 6
For Each sh In Worksheets
If sh.Name Like "Sheet1*" Or sh.Name Like "*Sheet2*" Then rw.Cells(i).Value = sh.Name & " Exists"
i = i + 1
Next
Set rw = rw.Offset(1, 0)
Next c
wbSrc.Close False
Next f
End Sub
'Return a collection of file objects given a starting folder and a file pattern
' e.g. "*.txt"
'Pass False for last parameter if don't want to check subfolders
Function GetFileMatches(startFolder As String, filePattern As String, _
Optional subFolders As Boolean = True) As Collection
Dim fso, fldr, f, subFldr
Dim colFiles As New Collection
Dim colSub As New Collection
Set fso = CreateObject("scripting.filesystemobject")
colSub.Add startFolder
Do While colSub.Count > 0
Set fldr = fso.GetFolder(colSub(1))
colSub.Remove 1
For Each f In fldr.Files
If UCase(f.Name) Like UCase(filePattern) Then colFiles.Add f
Next f
If subFolders Then
For Each subFldr In fldr.subFolders
colSub.Add subFldr.Path
Next subFldr
End If
Loop
Set GetFileMatches = colFiles
End Function
PUT THIS CODE IN AN XLSB OR XLSM EXCEL FILE IN THE SAME FOLDER AS YOUR EXCEL FILES.
It's probably just easier to do this kind of thing with Excel, and I'm a huge proponent of using the right tool for the job.
could please anybody tell me what's wrong with my vb.net code?
Dim service As New SpreadsheetsService("MySpreadsheetIntegration-v1")
' TODO: Authorize the service object for a specific user (see other sections)
service.setUserCredentials("xxx#gmail.com", "1234")
' Instantiate a SpreadsheetQuery object to retrieve spreadsheets.
' Instantiate a SpreadsheetQuery object to retrieve spreadsheets.
Dim query As New SpreadsheetQuery()
' Make a request to the API and get all spreadsheets.
Dim feed As SpreadsheetFeed = service.Query(query)
' TODO: There were no spreadsheets, act accordingly.
If feed.Entries.Count = 0 Then
End If
' TODO: Choose a spreadsheet more intelligently based on your
' app's needs.
Dim spreadsheet As SpreadsheetEntry = DirectCast(feed.Entries(0), SpreadsheetEntry)
Console.WriteLine(spreadsheet.Title.Text)
' Get the first worksheet of the first spreadsheet.
' TODO: Choose a worksheet more intelligently based on your
' app's needs.
Dim wsFeed As WorksheetFeed = spreadsheet.Worksheets
Dim worksheet As WorksheetEntry = DirectCast(wsFeed.Entries(0), WorksheetEntry)
' Define the URL to request the list feed of the worksheet.
Dim listFeedLink As AtomLink = worksheet.Links.FindService(GDataSpreadsheetsNameTable.ListRel, Nothing)
' Fetch the list feed of the worksheet.
Dim listQuery As New ListQuery(listFeedLink.HRef.ToString())
Dim listFeed As ListFeed = service.Query(listQuery)
' Create a local representation of the new row.
Dim row As New ListEntry()
row.Elements.Add(New ListEntry.Custom() With { _
Key .LocalName = "ldVorname", _
Key .Value = "Joe" _
})
' Send the new row to the API for insertion.
service.Insert(listFeed, row)
Your code looks correct. Make sure you have added both Imports.
Imports Google.GData.Client
Imports Google.GData.Spreadsheets
I suspect you did an automatic c# to vb.net conversion. Remove the Key syntax in your code like this:
' Create a local representation of the new row.
Dim row As New ListEntry()
row.Elements.Add(New ListEntry.Custom() With { _
.LocalName = "ldVorname", _
.Value = "Joe" _
})
However, running this code stil gives a GDataRequestException saying We're sorry, a server error occurred. Please wait a bit and try reloading your spreadsheet.
My project has the need to build consistent urls similar to the ones here on stackoverflow. I know how I "can" do it by running the string through multiple filters, but I'm wondering if I can do it all with a single method.
Basically I want to remove all special characters, and replace them with dashes BUT if there are multiple dashes in a row, I need them to be a single dash. How can I implement this as clean as possible?
Example: If I were to use the following string.
My #1 Event
My regex would create the following string
my--1-event
notice how there are two dashes (one for the space and one for the "#" symbol). What I need is
my-1-event
Here's how I'm implementing it currently
''# <System.Runtime.CompilerServices.Extension()>
Public Function ToUrlFriendlyString(ByVal input As String) As String
Dim reg As New Regex("[^A-Za-z0-9]")
''# I could run a loop filter here to match "--" and replace it with "-"
''# but that seems like more overhead than necessary.
Return (reg.Replace(Trim(input), "-"))
End Function
And then all I do is call the extension method
Dim UrlFriendlyString = MyTile.ToUrlFriendlyString
Thanks in advance.
Add a + to the end of the regex.
This will tell it to match one or more characters that match the character class that precedes the +.
Also, you should create your Regex instance in a Shared field outside the method so that .Net won't need to parse the regex again every time you call the method.
[edited by rockinthesixstring]: here's the final result
Private UrlRegex As Regex = New Regex("[^a-z0-9]+", RegexOptions.IgnoreCase)
<System.Runtime.CompilerServices.Extension()>
Public Function ToUrlFriendlyString(ByVal input As String) As String
Return (UrlRegex.Replace(Trim(input), "-"))
End Function
Another way I do this without using a regex and also is a little simpler to understand is the following:
Excuse me on my vb as I am mainly C# guy.
''# <System.Runtime.CompilerServices.Extension()>
Public Function ToUrlFriendlyString(ByVal input As String) As String
If [String].IsNullOrEmpty(s) = True Then
Return [String].Empty
End If
Dim builder As New StringBuilder()
Dim slug = input.Trim().ToLowerInvariant()
For Each c As Char in slug
Select Case c
Case ' '
builder.Append("-")
Case '&'
builder.Append("and")
Case Else
If (c >= '0' And c <= '9') OrElse (c >= 'a' And c <= 'z') And c != '-')
builder.Append(c)
End If
End Select
Next
Return builder.ToString()
End Function