Open an ofstream with tilde (~) in path name [duplicate] - c++

This question already has an answer here:
C++ paths beginning with ~ [duplicate]
(1 answer)
Closed 3 years ago.
I have to open some file for writing, and its name contains the tilde sign (~). The following code fails to create the desired text file. If I replace the ~ with /home/oren then everything works fine.
#include <fstream>
#include <string>
const std::string dirname = "/home/oren/GIT/";
// const std::string dirname = "~/GIT/";
const std::string filename = "someTextFile";
int main(int argc, char **argv)
{
std::ofstream log_file(dirname+filename+".txt");
log_file << "lorem ipsum";
log_file.close();
}
Is there any way to (easily) handle a file with ~ in its name?

The ~ shortcut in paths is not something magical at the filesystem level, opening ~/GIT literally tries to access ~/GIT, i.e: a file named GIT in the ~ directory. You can verify this by creating ~ and GIT first.
In the command line, ~ is typically resolved by your shell. e.g: in bash:
~ : The value of $HOME
https://www.gnu.org/software/bash/manual/html_node/Tilde-Expansion.html
Therefore, to achieve the same effect, you have to query the $HOME envvar, and replace the usage of leading ~/ in the path:
#include <stdlib.h>
const char* home = getenv("HOME")
if (home) { /* replace ~/ with home */ }
In addition, on linux, the wordexp function can be used to perform these replacements (~ to current user, ~other_user to home of other user)

The tilde is part of the shell expansion, it's not something handled by the underlying operating system. You need to resolve it yourself.
One simple way is to replace leading "~/" with the contents of the environment variable HOME (if it exists).

The tilde is expanded to the home directory by the shell. The iostreams don't use a shell, so you have to take care of the expansion for them. Tilde is a actually a valid character to use in a file name so without expansion, a file is created into a directory named ~ - which fails if the directory does not exist.
There is no standard way in C++ for shell expansions, nor a way to get the home directory, but there are several ways in POSIX systems:
wordexp is probably one of the most useful functions for this case. You can pass the path to the function and it will expand the tilde, as well as variables and braces. An example:
std::string full = dirname+filename+".txt"
wordexp_t p;
wordexp(full.c_str(), &p, 0);
std::string expanded = p.we_wordv[p.we_offs];
wordfree(&p);
std::ofstream log_file(expanded);
Other alternatives:
getpwuid gives you a structure with the home directory as a member. This can be used to get home directory of another user as well, in case that is needed.
HOME environment variable should also be available. It can be accessed with the standard std::getenv.

Related

Setting permissions to a directory using C++

I try to create new directory and set its permissions (using at most c++11 and without boost) so user, group and others can list files inside read them and write new files (linux environment).
#include <sys/stat.h>
#include <sys/types.h>
int main(void) {
const char* path = "/tmp/newDir";
mode_t process_mask = umask(0);
int syscall_status = mkdir(path, S_IRWXU | S_IRWXG | S_IRWXO);
umask(process_mask);
return syscall_status;
}
This code is based on the man (2) page of mkdir (and umask).
However the created directory has rwxr-xr-x permissions (no write for group and others).
I also tried using chmod syscall on the directory but it didn't solve the problem. Other sources in stackoverflow treated files (rather than folders), and trying to apply the file-methods on my directory didn't work as well.
Also, I want to avoid calling system() from stdlib, this is the last option I'll use if I don't find a solution (security considerations).
char* path = "/tmp/newDir";
Besides the syntax error, this is ill-formed since C++11. Prior to that, this would be using a deprecated conversion. String literals are const in C++ -> Use pointer to const.
Other than that, the program is correct assuming a POSIX system. If it fails, then you can check errno to see why. If you don't get all permissions: Check if the parent directory has a default ACL; that would override umask.
A portable way of creating a directory in C++ is std::filesystem::create_directory and a way of setting permissions is std::filesystem::permissions.

C++ code for copying FILES : : confused about relative address (tilde)

I've written a simple program to copy files.
It gets two strings :
1) is for the path of the source file.
2) is for name of a copy file.
It works correctly when I give it the absolute or relative path(without tilde sign (~)).
But when I give it a relative path with tilde sign (~) it can't find the address of a file. And it makes me confused !
Here is my sample input :
1) /Users/mahan/Desktop/Copy.cpp
2) ~/Desktop/Copy.cpp
The first one works correctly but the second one no.
And here is my code :
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
string path, copy_name;
cin >> path >> copy_name;
ifstream my_file;
ofstream copy(copy_name);
my_file.open(path);
if(my_file.is_open())
{
copy << my_file.rdbuf();
copy.close();
my_file.close();
}
}
The ~ is handled by the shell you're using to auto expand to your $HOME directory.
std::ofstream doesn't handle the ~ character in the filepath, thus only your first sample works.
If you pass the filepath to your program from the command line using argv[1], and call it from your shell, you'll get the ~ automatically expanded.
With what was said above, if you want to expand the ~ character yourself, you can use the std::getenv() function to determine the value of $HOME, and replace it with that value.
The second example does not work because the shell is what replaces ~ with $HOME, i.e. the path to your home directory.
fstream objects will not perform this replacement and will instead look for a directory actually called ~, which likely does not exist in your working directory.
std::ofstream can't handle ~. It is a shortcut to your home directory. You need to give absolute path of home or the relative path with respect to the code run directory for it to work.
To give relative path, For example, if you are running your code in Desktop directory, then you needn't give ~/Desktop/Copy.cpp. Just give Copy.cpp and it should suffice.

How do you create a folder in C++ with no path name just the name of the folder you want to create? [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Creating a directory In C or C++
I want to make a folder that is titled "BobtheBuilder". And then I want to create a text file inside of it. I want to do this without being aware of my path. I don't want to have to type in:
ofstream out("C:/MyComputer/User/Jeff/etc/BobtheBuilder/NewFile.txt");
I want it just to be local to this area where my executable is contained like this:
ofstream out("/BobtheBuilder/NewFile.txt");
is this possible? Do I have to know the whole path name in order to do file management? I feel like this is possible because you can create or open a file that is in the same directory as the program like:
ifstream inf("NewFile.txt");
Or is there a special keyword that fills in the previous path like this:
ifstream inf("FILLIN/BobtheBuilder/NewFile.txt");
Thanks
You can absolutely specify a relative path like "BobtheBuilder/NewFile.txt" without specifying the whole path.
You would however need to create the folder first before the file.
Since creating folders is platform specific and since you're on Windows, you would need to call the CreateDirectory function with "BobtheBuilder" as its parameter.
The folder would then be created in the default working directory of the program which is the same folder where the executable resides.
You can change this working directory using the SetCurrentDirectory function before creating the folder and file.
For creating a directory you can use the C function:
int mkdir(const char *pathname, mode_t mode);
If you can use Boost, then it really becomes easier and more C++ friendly:
bool create_directories(const path& p);
// usage example
boost::filesystem::create_directories("./BobtheBuilder");
As you mention in your question , you can use both absolute and relative paths. It just depends on what is your intention. In your case, you could just do:
boost::filesystem::create_directories("./BobtheBuilder");
ofstream out("./BobtheBuilder/NewFile.txt");
not needing to specify the absolute path at all.
If you often need to manage paths, Boost provides many useful tools for path management. Just as an example, consider the problem you mention in your question: you want to get the full path to the current directory and then append a relative path. You could do this very easily:
#include <boost/filesystem.hpp>
namespace fs = boost::filesystem;
...
fs::path curr_abs_path = fs::current_path();
fs::path rel_path = "foo/bar";
fs::path combined = (curr_abs_path /= rel_path);
cout << combined << endl;
Assuming the current directory is /tmp/ the previous code snippet would print:
/tmp/foo/bar
operator/= is responsible for appending two paths and returning the combined result.

My ifstream doesn't seem to be working

This is a main file that I am using to test methods before I implement them. I am trying to get the list of all files in a directory, write them to a txt file (It works fine until here), then read the file names from that text file.
using namespace std;
string sysCall = "", location = "~/Documents/filenames.txt";
string temp = "";
sysCall = "ls / > "+location;
system(sysCall.c_str());
ifstream allfiles(location.c_str());
allfiles.good();
getline(allfiles, temp);
cout<<temp<<endl; //At this point, the value of temp is equal to ""
return -1;
After the program runs, no text has been outputted. From what I've read in other peoples' questions, this should work (but obviously doesn't). What am I doing wrong here?
EDIT: allfiles.good() returns false, but I don't understand why it would return that...
ifstream allfiles("~/Documents/filenames.txt"); doesn't do what you think it does. The tilde ~ character is not part of the filename -- it is a special character interpreted by some shells. You need the entire path, with no ~ or $ characters in it.
Try setting location to "/tmp/filenames.txt", or just "filenames.txt".
Also, if Boost.Filesystem is available to you, you could use a directory_iterator instead of invoking /bin/ls.
I'll bet the system() call expands the ~ in the filename to your home directory (e.g. /home/mrswmmr), but ifstream does not. Replace the ~ with the full path to your home directory and it should work.
It has no guarantee to work because system gives no guarantee.

How to get directory of a file which is called from command line argument?

For example, I am calling my executable in ubuntu:
./foo temp/game1.txt temp/game2 txt
I am using realpath() to find the path to the game1.txt.
Using it however will give me the full path including the game1.txt name.
For example, it will come out as
/home/*/Download/temp/game1.txt
I want to erase game1.txt on that string so that I can use the string to use it to output other files in that folder.
Two questions.
Is realpath() alright to use for this kind of operation? Is there better way?
Can someone give me a quick way to erase "game1.txt" so that the string will be "/home/*/Download/temp/" save in a string format(not char)?
Thank you very much.
Don't really know Linux, but a general way for your second question:
#include <string>
#include <iostream>
int main(){
std::string fullpath("/home/*/Download/temp/game1.txt");
size_t last = fullpath.find_last_of('/');
std::string path = fullpath.substr(0,last+1);
std::cout << path;
}
See on Ideone.
You can use the dirname function for this: http://linux.die.net/man/3/dirname
Note that dirname will erase the trailing slash (except for the root directory); you'll have to append the slash back in when you append the filename.
Also, you don't actually need to use realpath for this purpose, you could simply use the argument as passed in, dirname will give you "temp" and you can append filenames to that.
man -S3 dirname
should do what you want
The cross-platform solution is
QFileInfo target_file_name(argv[1]);
QString absolute_path = target_file_name.absolutePath()
// e.g. /home/username/
QString some_other_file = QString("%1/another_file.txt").arg(absolute_path)
// => /home/username/another_file.txt
Boost.Filesystem can also do this easily. I just find the documentation of QFileInfo easily to navigate.
http://doc.qt.nokia.com/4.6/qfileinfo.html#absolutePath