Is it possible in Ruby running on Windows to resolve a local file to its UNC path.
For example: D:\hello.jpg should resolve to \\server01\DShare\hello.jpg
Drive letter D is shared as DShare on server01. I also want this to work for any given drive letter that is shared.
Similar question was asked in Python: Python 2: Get network share path from drive letter
If possible, I'd prefer for it to be achieved without installing additional Gems.
This code almost does it, but I want to use the Share name rather than the admin share $
def File.to_unc( path, server="localhost", share=nil )
parts = path.split(File::SEPARATOR)
parts.shift while parts.first.empty?
if share
parts.unshift share
else
# Assumes the drive will always be a single letter up front
parts[0] = "#{parts[0][0,1]}$"
end
parts.unshift server
"\\\\#{parts.join('\\')}"
end
Related
I have a list of filepaths relative to a root directory, and am trying to determine which would be matched by a glob pattern. I'm trying to get the same results that I would get if all the files were on my filesystem and I ran Dir.glob(<my_glob_pattern>) from the root diectory.
If this is the list of filepaths:
foo/index.md
foo/bar/index.md
foo/bar/baz/index.md
foo/bar/baz/qux/index.md
and this is the glob pattern:
foo/bar/*.md
If the files existed on my filesystem, Dir.glob('foo/bar/*.md') would return only foo/bar/index.md.
The glob docs mention fnmatch, and I tried using it but found that the pattern foo/bar/*.md was matching .md files in any number of nested subdirectories, similar to what Dir.glob('foo/bar/**/*.md') would, not just the direct children of the foo/bar directory:
my_glob = 'foo/bar/*.md'
filepaths = [
'foo/index.md',
'foo/bar/index.md',
'foo/bar/baz/index.md',
'foo/bar/baz/qux/index.md',
]
# Using the provided filepaths
filepaths_that_match_pattern = filepaths.select{|path| File.fnmatch?(my_glob, path)}.sort
# If the filepaths actually existed on my filesystem
filepaths_found_by_glob = Dir.glob(my_glob).sort
raise Exception.new("They don't match!") unless filepaths_that_match_pattern == filepaths_found_by_glob
I [incorrectly] expected the above code to work, but filepaths_found_by_glob only contains the direct children, while filepaths_that_match_pattern contains all the nested children too.
How can I get the same results as Dir.glob without having the file paths on my filesystem?
You can use the flag File::FNM_PATHNAME while calling File.fnmatch function. So your function call would look like this - File.fnmatch(pattern, path, File::FNM_PATHNAME)
You can see examples related to its usage here: https://apidock.com/ruby/File/fnmatch/class
Don't use File.fnmatch, instead use Pathname.fnmatch:
require 'pathname'
PATTERN = 'foo/bar/*.md'
%w[
foo/index.md
foo/bar/index.md
foo/bar/baz/index.md
foo/bar/baz/qux/index.md
].each do |p|
puts 'path: %-24s %s' % [
p,
Pathname.new(p).fnmatch(PATTERN) ? 'matches' : 'does not match'
]
end
# >> path: foo/index.md does not match
# >> path: foo/bar/index.md matches
# >> path: foo/bar/baz/index.md matches
# >> path: foo/bar/baz/qux/index.md matches
File assumes the existence of files or paths on the drive whereas Pathname:
Pathname represents the name of a file or directory on the filesystem, but not the file itself.
Also, regarding using Dir.glob: Be careful using it. It immediately attempts to find every file or path on the drive that matches and returns the hits. On a big or slow drive, or with a pattern that isn't written well, such as when debugging or testing, your code can be tied up for a long time or make Ruby or the machine Ruby's running on go to a crawl, and it only gets worse if you're checking a shared or remote drive. As an example of what can happen, try the following at your command-line, but be prepared to hit Cntrl+C to regain control:
ls /**/*
Instead, I recommend using the Find class in the Standard Library as it will iterate over the matches. See that documentation for examples.
I have a remote server I have read permission (in Windows I labeled it as my X: drive)
The correct path to a file I need to access is:
"X://some dir/some file"
The file that holds the Macro that is running exists in the C: drive. The code below checks to see if the path exists
If dir("X://some folder/some file", vbDirectory) = "" Then
Debug.Print "dir does not exist"
End If
Running that above enters the branch telling me the file does not exist. My questions are:
Do I have to put the name of the drive shorcut? (eg X: or can I put //: instead?)
How can I debug in Excel if I'm even allowed to enter the drive?
First, use backslashes for Windows paths. Second, don't use double backslashes when referring to a mapped drive. (Labelling the drive is meaningless to VBA.) eg:
X:\some folder\some file
If you didn't actually create a mapped drive, you will need to use the UNC or IP (and then you do use the double backslash) . EG:
\\remotehost\path\to\somefolder\somefile.txt
or
\\127.0.0.1\path\to\some folder\some file.txt
I think Tim is correct.
How can I debug in Excel if I'm even allowed to enter the drive?
If you RECORD macro opening a file in your X drive. You will see all the code that you need to put your original code working ;) . Every VBA coder do that kind of tricks.
Using Windows, I've experienced a slight annoyance when using __FILE__ to get the current location of a file or the absolute path of another file with
File.expand_path("lib/other", File.dirname(__FILE__))
This doesn't work though if the folder has characters like äöüè and similar. This get's especially annoying if the windows username of a client contains such a character and my script necessarily lives inside the %appdata% folder.
To demonstrate my problem, C:\äüé\test.rb contains only
puts __FILE__
Running it:
> ruby C:\äüé\test.rb
C:/"?'/test.rb
Is there a reliable way to get the current file path?
I am using Dropbox Ruby API. When I receive the "path" of the directories on the Dropbox server via its API, the directory paths are capitalized if they are directly under the Dropbox root directory, irrespective of whether the corresponding directory on the local computer is capitalized. Given the information on the Dropbox server, how can I achieve the corresponding path on the local computer with the correct alphabet case? Simply applying downcase to the given path does not work because some directories on the local computer might actually be capitalized.
You could try a case insensitive search for the file in question or just use a case insensitive regex in general. Just be sure to match the full file name unlike the example below:
require 'find'
Find.find('.') do |path|
if path =~ /file_name/i
p path
end
end
I'm trying to crawl FTP and pull down all the files recursively.
Up until now I was trying to pull down a directory with
ftp.list.each do |entry|
if entry.split(/\s+/)[0][0, 1] == "d"
out[:dirs] << entry.split.last unless black_dirs.include? entry.split.last
else
out[:files] << entry.split.last unless black_files.include? entry.split.last
end
But turns out, if you split the list up until last space, filenames and directories with spaces are fetched wrong.
Need a little help on the logic here.
You can avoid recursion if you list all files at once
files = ftp.nlst('**/*.*')
Directories are not included in the list but the full ftp path is still available in the name.
EDIT
I'm assuming that each file name contains a dot and directory names don't. Thanks for mentioning #Niklas B.
There are a huge variety of FTP servers around.
We have clients who use some obscure proprietary, Windows-based servers and the file listing returned by them look completely different from Linux versions.
So what I ended up doing is for each file/directory entry I try changing directory into it and if this doesn't work - consider it a file :)
The following method is "bullet proof":
# Checks if the give file_name is actually a file.
def is_ftp_file?(ftp, file_name)
ftp.chdir(file_name)
ftp.chdir('..')
false
rescue
true
end
file_names = ftp.nlst.select {|fname| is_ftp_file?(ftp, fname)}
Works like a charm, but please note: if the FTP directory has tons of files in it - this method takes a while to traverse all of them.
You can also use a regular expression. I put one together. Please verify if it works for you as well as I don't know it your dir listing look different. You have to use Ruby 1.9 btw.
reg = /^(?<type>.{1})(?<mode>\S+)\s+(?<number>\d+)\s+(?<owner>\S+)\s+(?<group>\S+)\s+(?<size>\d+)\s+(?<mod_time>.{12})\s+(?<path>.+)$/
match = entry.match(reg)
You are able to access the elements by name then
match[:type] contains a 'd' if it's a directory, a space if it's a file.
All the other elements are there as well. Most importantly match[:path].
Assuming that the FTP server returns Unix-like file listings, the following code works. At least for me.
regex = /^d[r|w|x|-]+\s+[0-9]\s+\S+\s+\S+\s+\d+\s+\w+\s+\d+\s+[\d|:]+\s(.+)/
ftp.ls.each do |line|
if dir = line.match(regex)
puts dir[1]
end
end
dir[1] contains the name of the directory (given that the inspected line actually represents a directory).
As #Alex pointed out, using patterns in filenames for this is hardly reliable. Directories CAN have dots in their names (.ssh for example), and listings can be very different on different servers.
His method works, but as he himself points out, takes too long.
I prefer using the .size method from Net::FTP.
It returns the size of a file, or throws an error if the file is a directory.
def item_is_file? (item)
ftp = Net::FTP.new(host, username, password)
begin
if ftp.size(item).is_a? Numeric
true
end
rescue Net::FTPPermError
return false
end
end
I'll add my solution to the mix...
Using ftp.nlst('**/*.*') did not work for me... server doesn't seem to support that ** syntax.
The chdir trick with a rescue seems expensive and hackish.
Assuming that all files have at least one char, a single period, and then an extension, I did a simple recursion.
def list_all_files(ftp, folder)
entries = ftp.nlst(folder)
file_regex = /.+\.{1}.*/
files = entries.select{|e| e.match(file_regex)}
subfolders = entries.reject{|e| e.match(file_regex)}
subfolders.each do |subfolder|
files += list_all_files(ftp, subfolder)
end
files
end
nlst seems to return the full path to whatever it finds non-recursively... so each time you get a listing, separate the files from the folders, and then process any folder you find recrsively. Collect all the file results.
To call, you can pass a starting folder
files = list_all_files(ftp, "my_starting_folder/my_sub_folder")
files = list_all_files(ftp, ".")
files = list_all_files(ftp, "")
files = list_all_files(ftp, nil)