Solved

C++ Overwrite txt files in all subdirectories of folder with "100"

Posted on 2004-03-29
21
483 Views
Last Modified: 2007-12-19
I did post a similar question about this before but it was for vbs. I am playing around with C++ a bit and windered if it would be possible to do it in C++ instead.

What I am trying to do is overwrite / truncate all .txt and possibly .ini files in a directory and it's subdirectories with "100" without quotes.

It is to form the small app to reset all print credits to 100 every week.

How can I do this?

Many thanks,
S-P.
0
Comment
Question by:Sparky-Plug
  • 5
  • 4
  • 3
  • +2
21 Comments
 
LVL 12

Expert Comment

by:stefan73
ID: 10705108
Hi Sparky-Plug,
First of all: This is a task better suited for Perl or even a batch file...

The easiest way in C/C++ is via the glob function:

#include <glob.h>


main(){
    glob_t buf;
    int i;

    buf.gl_offs = 0;

    glob ("*.txt", GLOB_DOOFFS, NULL, &globbuf);
    glob ("*.ini", GLOB_DOOFFS|GLOB_APPEND, NULL, &globbuf);

    for(i=0;i<buf.gl_pathc;i++)
        printf("Match %d: %s\n",i,buf.gl_pathc[i]);
}


Via opendir/readdir/closedir you can also get a directory content, which is probably the better way for recursion:
if (entry is a directory)
    recurse(entry)
else if (entry matches pattern)
    write_100

etc.


Cheers,
Stefan
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 10705526
Stefan,

if he played with vbs before, he is most likely on a Windows platform where glob.h normally doesn't exist.

Sparky,

what do you mean by truncating/overwrite with  "100". Do you want to have 100 as (only) contents of all  .txt and .ini files?

If so, a batch file will do that like this

@echo off

if "%1"=="" goto ERR
for /F %%i in ('dir /s /b %1\*.txt') do echo 100> %%i
for /F %%i in ('dir /s /b %1\*.ini') do echo 100> %%i
goto END

:ERR
echo.
echo Missing argument. Expecting
echo.
echo      %0 full_directory_path
echo.
echo For example:
echo.
echo      %0 C:\temp\myTempFiles
echo.

:END

Regards, Alex
0
 
LVL 17

Expert Comment

by:rstaveley
ID: 10705733
If you do want to use C/C++ on Windoze, here is the source code for a recursive directory listing utility written in C rather than C++, which you can hack to suit your purposes. Look for the --> marker and make your edit at that point:
--------8<--------
#include <windows.h>
#include <stdio.h>
#include <string.h>

int processdirectory(LPCTSTR);
void error(LPCTSTR);

int main(int argc,char **argv)
{
      if (argc < 2)
            return fprintf(stderr,"Usage: %s {directoryname}\n",*argv),1;

      while (--argc)
            processdirectory(*++argv);

      return 0;
}

/*
 * Process all files and subdirectories in the directory
 */
int processdirectory(LPCTSTR directoryname)
{
WIN32_FIND_DATA fdata;
HANDLE handle;
char *filespec;
char *inner_directoryname;

/* Allocate memory for the file specification */

      if ((filespec = malloc(strlen(directoryname)+4+1)) == NULL)
            return error("Out of memory"),1;

/* Format the file specification, using the directory name */

      sprintf(filespec,"%s\\*.*",directoryname);

/*      fprintf(stderr,"Processing \"%s\"\n",filespec);/**/

/* Look for *.* in the directory */

         if ((handle = FindFirstFile(filespec,&fdata)) == INVALID_HANDLE_VALUE)
              return error("Invalid filespec"),free(filespec),2;

/* Print the file names */

      do {
      char *full_filename;
      DWORD attrs;

            if ((full_filename = malloc(strlen(directoryname)+strlen(fdata.cFileName)+1+1)) == NULL)
                  return error("Out of memory"),free(filespec),1;

/* Get the fully qualified filename */

            sprintf(full_filename,"%s\\%s",directoryname,fdata.cFileName);

/* Read off the file attributes */

              attrs = GetFileAttributes(full_filename);

/* Handle all non-directories */

            if (!(attrs & FILE_ATTRIBUTE_DIRECTORY)) {

/* ---> Here's where you should do your hacking, instead of printing the filename, why not overwrite the file with your 100.... */

                  printf("%s\n",full_filename);
            }

/* Recurse into directories */

            else if (strcmp(fdata.cFileName,".") && strcmp(fdata.cFileName,".."))
                  processdirectory(full_filename);

            free(full_filename);

      } while (FindNextFile(handle,&fdata));

/* Deallocate filespec */

      free(filespec);

/* Close the search handle */

      FindClose(handle);
      return 0;
}

/*
 * Error handler
 */
void error(LPCTSTR message)
{
      fprintf(stderr,"Error: %s\n",message);
      /*exit(1);*/
}
--------8<--------
0
 

Author Comment

by:Sparky-Plug
ID: 10706424
Hi,

The platform is indeed Windoze and I do only want to write 100 on it's own to the files.

itsmeandnobodyelse, That code does not seem to work fo me. It just creates files called Copy and New in the directory specified. The files do contain 100 but none of the other files have been changed.

I was using XP so the /f switch should have worked too. Any ideas?

Thanks for the replys!
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 10707695
Did you copy-paste the contents from above and stored it to a file - maybe called set100.bat ?

It would be best if the batch-file is stored in the directory where you start the search. Alternatively, you may store it to a directory that is in the system path. You find out these directories by typing PATH<Enter> at the command prompt of a command window.

Then type the following always followed by <Enter>

   C:                               // if that is the drive where your directory is
   cd \                             // goto root
   cd  <directory>            // enter the full path here, e. g.  cd   temp\myfolder  
                                     // if there are spaces in the folder names use quotes "", e. g. cd "temp\my folder\my subdir"
   set100 .                       // using the period you don't need to quote folder names with spaces.

Then, all files having .txt or .ini extension get updated. You can get a report of all files by typing

   dir /s /b *.txt

or

   dir /s /b *.ini

>> called "Copy and New"

Did your explorer show file extensions? Maybe  COPY and NEW have extension COPY.txt or NEW.ini ? The batch file cannot have created files having those names.

As the batch-file had worked here on my Win2K environment, i am absolutely sure it will work on XP as well. You must know that it is one of my jobs to write batch files ;-)

To debug the batch file you may comment first statement. Then, if you have folder names that contain spaces, you should quote all occurences of the %1 argument.

Note, it is  "  %1.txt "  '   but no spaces!!!

REM @echo off

if "%1"=="" goto ERR
for /F %%i in ('dir /s /b "%1\*.txt"') do echo 100> %%i
for /F %%i in ('dir /s /b "%1\*.ini"') do echo 100> %%i
goto END

:ERR
echo.
echo Missing argument. Expecting
echo.
echo      %0 full_directory_path
echo.
echo For example:
echo.
echo      %0 C:\temp\myTempFiles
echo.

:END



Regards, Alex
0
 
LVL 8

Expert Comment

by:mnashadka
ID: 10709561
A C++ solution might look like this (which does work).  It's similar to rstaveley's solution, but approaches it more from a C++ standpoint.  Good luck.

#include <string>
#include <fstream>
#include <windows.h>

using namespace std;
// Function to process a directory
void ProcessDirectory(std::string directory)
{
  // Read through the directory
  WIN32_FIND_DATA fd;
  HANDLE hFind = FindFirstFile((directory + "\\*.*").c_str(), &fd);
  if(hFind == INVALID_HANDLE_VALUE)
  {
    // Error
    return;
  }
  do
  {
    // Make sure that it's not the . or .. directories
    if(strcmp(fd.cFileName, ".") && strcmp(fd.cFileName, ".."))
    {
      string full_name = directory + string("\\") + fd.cFileName;
      // If it's a directory, call this recursively
      if(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
      {
        ProcessDirectory(full_name);
      }
      else // If it's a file, open it and dump the text
      {
        ofstream out(full_name.c_str());
        out << "100";
      }
    }
  } while(FindNextFile(hFind, &fd)); // Get the next file
  // Cleanup
  FindClose(hFind);
}

// Main function just calls ProcessDirectory with the starting point
void main(void)
{
  ProcessDirectory("c:\\test");
}
0
 

Author Comment

by:Sparky-Plug
ID: 10711118
That batch file does mostly work. Those two files (no extensions) were made ahen it came to a file and folder with spaces (copy of.. and New Folder - for testing). It doesn't seem to be able to go into any directory that is named with a space or a file name with spaces.

mnashadka, when it I try to compile that code it returns the error 'main' must return 'int' on line 41 (brace above ProcessDirectory("c:\\test");)

Thanks for the help and sorry to put a dampener on things!
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 10711277
if you have file names and folders with spaces change

    for /F %%i in ('dir /s /b "%1\*.txt"') do echo 100> %%i
    for /F %%i in ('dir /s /b "%1\*.ini"') do echo 100> %%i

to

   for /F %%i in ('dir /s /b "%1\*.txt"') do echo 100> "%%i"
   for /F %%i in ('dir /s /b "%1\*.ini"') do echo 100> "%%i"

and pass the folder name within quotes:

  set100 "c:\temp\my folder\my sub folder"

The problem is, that %1 isn't correct if the folder name has spaces and isn't quoted.

Regards, Alex

0
 
LVL 12

Expert Comment

by:stefan73
ID: 10711669
itsmeandnobodyelse,
> batch file

You need to create recursion by doing CALL of you bat somewhere.

I've only tried this out with 4NT, though. I don't use normal BAT files for anything more complicated, since they're a PAIN.

Stefan
0
Why You Should Analyze Threat Actor TTPs

After years of analyzing threat actor behavior, it’s become clear that at any given time there are specific tactics, techniques, and procedures (TTPs) that are particularly prevalent. By analyzing and understanding these TTPs, you can dramatically enhance your security program.

 
LVL 12

Accepted Solution

by:
stefan73 earned 24 total points
ID: 10711678
itsmeandnobodyelse,
> Windows platform where glob.h normally doesn't exist

I have it with my cygwin ;-)

Sparky-Plug: Try cygwin at http://www.cygwin.com

Stefan
0
 
LVL 17

Expert Comment

by:rstaveley
ID: 10712083
<DIGRESSION>

mnashadka, don't you hate the way that the Windows API forces you to do ugly things like .c_str() with std::string, because of all of the legacy char* functions? I'm actually finding myself warming to MFC's CString so that you can have the more natural looking...
--------8<--------
// Function to process a directory
void ProcessDirectory(const CString& directory)
{
  // ...
  FindFirstFile(directory + "\\*.*", &fd);
  // ...
}
--------8<--------
...just for the sake of the API.

Compare the two:

(1) Using STL
--------8<--------
#include <iostream>
#include <string>

void f(const char*); // Legacy function which doesn't use std::string

int main()
{
      const std::string str = "Hello, world";
      f(str.c_str()); // Ugh - convert it to a C-style string
}

void f(const char* str)
{
      std::cout << str << '\n';
}
--------8<--------

(2) Using MFC
--------8<--------
#include <afxwin.h>  // CString definition (need to link with mfc71.lib)
#include <iostream>

void f(const char*); // Legacy function which doesn't use std::string

int main()
{
      const CString str = "Hello, world";
      f(str); // No conversion needed CString has a LPCTSTR operator which converts it to a C-style string
}

void f(const char* str)
{
      std::cout << str << '\n';
}
--------8<--------

The first looks bad simply because f() is using a "legacy" function which takes a const char*, and std::string doesn't have a const char* operator (or what the Windoze world calls a LPCTSTR operator). f() should be overloaded or simply replaced by a const std::string equivalent.

Similarly, I'd like to see the FindFirstFile API function having a const std::string& overload, now that the 1998 C++ standard is pretty much with us, but I can't see that happening now that we're all dancing to the tune of .NET.

</DIGRESSION>
0
 
LVL 8

Expert Comment

by:mnashadka
ID: 10713980
Sparky-Plug,
Depending upon the compiler, you may have to change void main() to int main() and then return 0 at the end.

rstaveley,
I agree that casting to c_str is a bit annoying, but it's much better than including MFC just for the CString class.  I know that they separated it in the .NET compiler, but it's still another non-standard item to include.  I prefer std::string (or std::basic_string<char>) much more for a couple of reasons.  One is that I do quite a bit of work on Unix as well as Windows, and I don't like having to switch string classes when switching environments.  The other, bigger one is that the standard version works much better with STL.  When you insert it into an ofstream, you can just do std::cout << str (rather than std::cout << str.GetBuffer(0), which is just as annoying as calling c_str() to go the other way), and you can use the STL functions like transform, find_last_of, find_last_not_of, etc. since the std::string has iterators (begin(), end(), rbegin(), rend()) built into it.  Actually, what we did was derived a class from std::string and overloaded the const char * operator, and now we don't have the calling c_str() problem anymore.  That's just my 2 cents.
0
 

Author Comment

by:Sparky-Plug
ID: 10714627
Sorry to be a pain here.

I've already tried the batch file like that and it didn't make any difference to changing anything beyond the spaced file / folder name.

Also, mnashadka, I changed that code to this
HANDLE hFind = FindFirstFile((directory + "\\*.ini").c_str(), &fd);
so that it doesn't change all files but it stopped it from changing any files in subdirectories. What have I done wrong?

Once again thanks for your info and I apolagise for being so pathetic at this :-)
0
 
LVL 8

Expert Comment

by:mnashadka
ID: 10714679
If you search all of the ini files, you'll only get the ini files.  The directories won't be included.  You might want to change the else statement
else // If it's a file, open it and dump the text
      {
        ofstream out(full_name.c_str());
        out << "100";
      }
to look for .ini or .txt in the cFileName (or full_name) objects.  Keep in mind that you may have to ignore case.  Something like:
if(full_name.length() > 4)
{
  string extension = full_name.substr(full_name.length() - 3);
  transform(extension.begin(), extension.end(), extension.begin(), toupper);
  if(extension == "TXT" || extension == "INI")
  {
        ofstream out(full_name.c_str());
        out << "100";
  }
}

I didn't test any of this out, so forgive me if there's a syntax error or something.  Good luck.
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 10715610
Ok, here is the batch file that can handle spaces also

@echo off

if not exist %1 goto ERR

if /i "%2"=="show" goto SHOWONLY
goto SET100

:SHOWONLY
for /F "delims=" %%i in ('dir /s /b %1\*.txt') do echo "%%i"
for /F "delims=" %%i in ('dir /s /b %1\*.ini') do echo "%%i"

goto END

:SET100
for /F "delims=" %%i in ('dir /s /b %1\*.txt') do echo 100>"%%i"
for /F "delims=" %%i in ('dir /s /b %1\*.ini') do echo 100>"%%i"

goto END

:ERR
echo.
echo Missing argument. Expecting
echo.
echo      %0 full_directory_path
echo.
echo For example:
echo.
echo      %0 C:\temp\myTempFiles
echo.

:END


The problem was that space is a default delimiter that must be switched off (by "delims=").

If you store it as 'set100.bat' you have to call it like that

   set100 "folder with spaces"

and it will update all .txt and .ini files.

If you want to test before what files are going to be found, call

   set100 "folder with spaces" show

and you get a report of all files that will get updated.

Regards, Alex
0
 
LVL 17

Assisted Solution

by:rstaveley
rstaveley earned 23 total points
ID: 10715887
mnashadka, CString works fine with ostream operator<< in VC 7.1 without the GetBuffer(0) trick. The .exe size is the same for the std::string and CString versions of my previous code too.

I'm curious to know if you've had any problems with unexpected behaviour with your class derived from std::string with the const char* operator. It seems like an odd omission from the standard library. They must have had some reason to leave it out?
0
 
LVL 39

Assisted Solution

by:itsmeandnobodyelse
itsmeandnobodyelse earned 23 total points
ID: 10716099
If you add an operator const char* to std::string you had to add an operator const wchar_t * also...

However, you'll get ambiguity errors if both LPCSTR and LPCWSTR can be used as function argument. It should be possible to use the template argument when defining the cast operator. But that isn't valid, i suppose.

BTW, i think that it was a big mistake when designing std::string to put the main focus to the UNICODE and wide character capabilities. My own string class - although limited to char - has ten times more functionality and if i would need wide characters, i would easily create a new class.  

Regards, Alex
         
0
 
LVL 8

Expert Comment

by:mnashadka
ID: 10716172
I really haven't had any problems with the internal const char *.  Like itsmeandnobodyelse said (sort of), std::basic_string was designed to handle multiple character representations (char *, wchar_t, my_char), and std::string is just a typedef for std::basic_string<char>, so I'm sure that's why they didn't add it to the standard library.  I don't see any reason to add the operator const wchar_t *, since I don't use UNICODE.
0

Featured Post

Top 6 Sources for Identifying Threat Actor TTPs

Understanding your enemy is essential. These six sources will help you identify the most popular threat actor tactics, techniques, and procedures (TTPs).

Join & Write a Comment

What is C++ STL?: STL stands for Standard Template Library and is a part of standard C++ libraries. It contains many useful data structures (containers) and algorithms, which can spare you a lot of the time. Today we will look at the STL Vector. …
This article shows you how to optimize memory allocations in C++ using placement new. Applicable especially to usecases dealing with creation of large number of objects. A brief on problem: Lets take example problem for simplicity: - I have a G…
The viewer will learn how to user default arguments when defining functions. This method of defining functions will be contrasted with the non-default-argument of defining functions.
The viewer will learn additional member functions of the vector class. Specifically, the capacity and swap member functions will be introduced.

759 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

23 Experts available now in Live!

Get 1:1 Help Now