string-append to files in a directory with Scheme - filesystems

Please note first of all that this is a homework question so I'm not looking for straight code or anything like that, just for someone to maybe help me with my logic.
The assignment is in DrRacket. The question asks:
Given a FileSystem, which we've defined as a structure with two fields, name and contents, where contents is a list of either directories or files; write a function that will create a ".bak" filename for every file in the directory and place it immediately after the file.
I am totally lost. My logic is as follows: If the first thing in the content list is a file, simply remake the directory with that file and a new file with ".bak" appended. This is as far as I can get - I can't see how to work things out if there's a subdirectory, OR how to go about moving further down the list.
Here's my atrocious code:
(define (backup my-fs)
(cond
[(empty? (dir-contents my-fs)) empty]
[(file? (first (dir-contents my-fs))) (make-dir (dir-name my-fs) (append (backup-list (first (dir-contents my-fs)))(rest (dir-contents my-fs))))]
[(dir? (first (dir-contents my-fs))) (backup (first (dir-contents my-fs)))]))
Can anyone help me reason this out?

The contents part of your FileSystem is a list containing files or directories (which are lists containing ....).
This is a basic tree-traversal problem where you have three cases, as you noted:
list is empty
first element in list is a file
first element in list is a directory
Then you need an action for each case:
done
keep that filename, create a new filename, and continue processing the rest of the list
keep that directory, recursing over it, and continue processing the rest of the list
For example:
(define (traverse contents)
(cond
[(empty? contents) ... nothing to do ...]
[(file? (first contents)) ;; if the first element's a file:
(cons (first contents) ;; keep the file
(cons (... make backup filename ... (first contents)) ;; make the backup
(traverse (rest contents))))] ;; and recurse on the rest
[(dir? (first contents) ;; if the first element's a directory:
(cons (traverse (first contents)) ;; recurse on the first
(traverse (rest contents)))])) ;; and also recurse on the rest

You need to clarify your data definition. You write:
"Given a FileSystem, which we've defined as a structure with two fields, name and contents, where contents is a list of either directories or files; write a function that will create a ".bak" filename for every file in the directory and place it immediately after the file. "
This makes it clear what a FileSystem is... if you know what "directories" and "files" are. You need to clarify this by writing data definitions for "directory" and "file". Each of these should be a separate sentence. They might be really simple, e.g. "A file is represented as a string".
After doing this, write some examples of FileSystems.

Related

How to print the file tree of only the found files in a recursive function?

I have a recursive function that searches a path for a given file name. What I am trying to do is to print the files that match, along with their parent directories.
So for a file tree like this:
mydir
mysubdir
mysubsubdir
file1
file2
file1
mysubdir2
file2
I want to print this when I search for file1:
mydir
mysubdir
mysubdir
file1
file1
I am able to see each found files' paths, so I thought of constructing a new tree from those paths and then printing that tree, but It seems to me that there must be a much simpler way.
Your function needs the path from the root to the current directory that you are processing. For example, via a const char ** argument, and append to each time you descent a directory (linked list if you don't like recalloc or ensure sufficiently large size up front). When there is match you can print the path starting from the root (see below though).
To get the short-cut behavior of mydir/file1, you need the path the previous match. This could be another const char ** argument. The print rule is now refined to indent as many levels as previous and current match have common path elements, then print the remaining unique path from current match. This implies a depth first search and sub-directories are visited in sorted order.
Instead of printing as you go along, you can also record each match in const char *** or as tree as suggested by #wildplasser. Then loop/walk through the result using the same refined print algorithm (if you use a tree you only need to know the level not the prefix path). If you don't do a depth first search, you can sort the array, in this approach. And if you use a tree to store the result, you walked the depth first.

Asynchronous copy-directory with the option to ignore subdirectory in Emacs-lisp?

I am using the code from the answer on this problem for asynchronous copy-directory for a few months now, but sometimes I need one or more subdirectories to be ignored. Is there an easy way by slightly modifying the code to do that?
I have tried to use Selective Directory Copying: SDC package from here, but it brakes when file or folder already exists.
This is the code I am using right now:
(async-start
`(lambda()
(copy-directory ,"~/Documents/data/" ,"~/Dropbox/data_backup/" t t t)
,"~/Documents/data/")
(lambda(return-path)
(message "Upload '%s' finished" return-path)))
There is a subdirectory in ~/Documents/data that sometimes I want it to be ignored because it is larger than a threshold.
copy-directory calls itself recursively. You can use cl-flet to redefine it locally, while keeping the original definition. You can also do this with advice (and actually this cl-flet technique seems to break advice), but then it's effectively globally redefining the function and you need to control it with e.g. variables.
(defun jpk/copy-directory (directory newname &optional keep-time parents copy-contents)
(cl-letf (((symbol-function 'orig/copy-directory) (symbol-function 'copy-directory))
((symbol-function 'copy-directory)
(lambda (directory newname &optional keep-time parents copy-contents)
(if (string= directory "/path/to/foo")
(message "skipping: %s" directory)
(orig/copy-directory directory newname keep-time parents copy-contents)))))
(copy-directory directory newname keep-time parents copy-contents)))
In more detail: store the original function to orig/copy-directory, replace the function copy-directory with a lambda that calls orig/copy-directory only if the directory name doesn't match some string, then call the new definition of copy-directory. The recursive call to copy-directory also uses the new definition. All of this is wrapped up in jpk/copy-directory. To make it more flexible, you could add a predicate argument to jpk/copy-directory so the test isn't hard coded.

Emacs lisp: Concise way to get `directory-files` without "." and ".."?

The function directory-files returns the . and .. entries as well. While in a sense it is true, that only this way the function returns all existing entries, I have yet to see a use for including these. On the other hand, every time a use directory-files I also write something like
(unless (string-match-p "^\\.\\.?$" ...
or for better efficiency
(unless (or (string= "." entry)
(string= ".." entry))
..)
Particularly in interactive use (M-:) the extra code is undesirable.
Is there some predefined function that returns only actual subentries of a directory efficiently?
You can do this as part of the original function call.
(directory-files DIRECTORY &optional FULL MATCH NOSORT)
If MATCH is non-nil, mention only file names that match the regexp MATCH.
so:
(directory-files (expand-file-name "~/") nil "^\\([^.]\\|\\.[^.]\\|\\.\\..\\)")
or:
(defun my-directory-files (directory &optional full nosort)
"Like `directory-files' with MATCH hard-coded to exclude \".\" and \"..\"."
(directory-files directory full "^\\([^.]\\|\\.[^.]\\|\\.\\..\\)" nosort))
although something more akin to your own approach might make for a more efficient wrapper, really.
(defun my-directory-files (directory &optional full match nosort)
"Like `directory-files', but excluding \".\" and \"..\"."
(delete "." (delete ".." (directory-files directory full match nosort))))
although that's processing the list twice, and we know there's only one instance of each of the names we wish to exclude (and there's a fair chance they'll appear first), so something more like this might be a good solution if you're expecting to deal with large directories on a frequent basis:
(defun my-directory-files (directory &optional full match nosort)
"Like `directory-files', but excluding \".\" and \"..\"."
(let* ((files (cons nil (directory-files directory full match nosort)))
(parent files)
(current (cdr files))
(exclude (list "." ".."))
(file nil))
(while (and current exclude)
(setq file (car current))
(if (not (member file exclude))
(setq parent current)
(setcdr parent (cdr current))
(setq exclude (delete file exclude)))
(setq current (cdr current)))
(cdr files)))
If you use f.el, a convenient file and directory manipulation library, you only need function f-entries.
However, if you don't want to use this library for some reason and you are ok for a non-portable *nix solution, you can use ls command.
(defun my-directory-files (d)
(let* ((path (file-name-as-directory (expand-file-name d)))
(command (concat "ls -A1d " path "*")))
(split-string (shell-command-to-string command) "\n" t)))
The code above suffice, but for explanation read further.
Get rid of dots
According to man ls:
-A, --almost-all
do not list implied . and ..
With split-string that splits a string by whitespace, we can parse ls output:
(split-string (shell-command-to-string "ls -A"))
Spaces in filenames
The problem is that some filenames may contain spaces. split-string by default splits by regex in variable split-string-default-separators, which is "[ \f\t\n\r\v]+".
-1 list one file per line
-1 allows to delimit files by newline, to pass "\n" as a sole separator. You can wrap this in a function and use it with arbitrary directory.
(split-string (shell-command-to-string "ls -A1") "\n")
Recursion
But what if you want to recursively dive into subdirectories, returning files for future use? If you just change directory and issue ls, you'll get filenames without paths, so Emacs wouldn't know where this files are located. One solution is to make ls always return absolute paths. According to man ls:
-d, --directory
list directory entries instead of contents, and do not dereference symbolic links
If you pass absolute path to directory with a wildcard and -d option, then you'll get a list of absolute paths of immediate files and subdirectories, according to How can I list files with their absolute path in linux?. For explanation on path construction see In Elisp, how to get path string with slash properly inserted?.
(let ((path (file-name-as-directory (expand-file-name d))))
(split-srting (shell-command-to-string (concat "ls -A1d " path "*")) "\n"))
Omit null string
Unix commands have to add a trailing whitespace to output, so that prompt is on the new line. Otherwise instead of:
user#host$ ls
somefile.txt
user#host$
there would be:
user#host$ ls
somefile.txtuser#host$
When you pass custom separators to split-string, it treats this newline as a line on its own. In general, this allows to correctly parse CSV files, where an empty line may be valid data. But with ls we end up with a null-string, that should be omitted by passing t as a third parameter to split-string.
How about just using remove-if?
(remove-if (lambda (x) (member x '("." "..")))
(directory-files path))

How can I search for a file within a directory and its sub-directories in c?

How can I search for a file within a directory and its sub-directories in C?
I'm not allowed to use find and I must use opendir , readdir and stat.
I want to perform something like the command ls -ln if the file indeed exists.
For traversing the directories, you will need: opendir(3), readdir(3), and closedir(3).
For checking the type of file (to see if it's a directory and if you should recursively search within it) you will need stat(2).
You will want to check
(struct stat).st_mode & S_IFDIR
to see if the file is a directory. See <sys/stat.h> for more information.
If we try to write a small piece of code in C then we can do this search activity easily.
Suppose you need to search abc.txt in a /home/Jack/ then just open a file stream and pass the file path as a parameter.
Now when this statement will be executed, it will try to open the existing file. This API will return non zero if the file exists otherwise it is returned -1 or zero.
You've already provided the basic answer: opendir/readdir/closedir. As you walk the directory entries, you check whether each refers to a file or a directory. Those that refer to directories, you traverse as well (typically recursively). For those that refer to files, you compare their names to the file(s) you're looking for, and see if you've found it.
One other minor detail: you probably also want to check for symbolic links. A symbolic link can (for example) refer to a parent directory, which could/can lead to infinite recursion. You may want to ignore symbolic links completely, or you may want to keep a list of directories you've already at least started to traverse, and resolve/traverse what's in the symbolic link only if it's not already in the list.

Open every file but not links to other directories when using scandir()

I want to recursively copy one directory into another (like cp -R) using POSIX scandir().
The problem is that when I copy a directory like /sys/bus/, which contains links to higher levels (for example: foo/foo1/foo2/foo/foo1/foo2/foo/... ) the system enters a loop status and copies the directories "in the middle" forever...
How can I check if the file I'm opening with dirent is a link or not?
Look at this: How to check whether two file names point to the same physical file
You need to store a list of inodes that you have visited to make sure that you don't get any duplicates. If you have two hard links to the same file, there is no "one" canonical name. One possibility is to first store all the files and then recurse through all the filenames. You can store the path structure separately from the inodes and file contents.

Resources