Find/Match variable filenames and move files to respective directory - bash

I've never come to SO asking "Do my homework" but I really don't know where to start with this one.
I have a load of documents which are dumped in a directory after being auto-signed using JSignPdf (--output-directory option seemingly has no ability to output to same as input):
/some/dir/Signed/PDF1_signed.pdf
/some/dir/Signed/PDF2_signed.pdf
/some/dir/Signed/PDF2_signed.pdf
I'd like to then find their source/unsigned counterparts:
/some/dir/with/docs/PDF1.pdf
/some/dir/where/is/PDF2.pdf
/some/dir/why/this/PDF3.pdf
...and move the signed PDFs into the respective directories.
I use the command, to find all the PDFs in the variety of directories:
find . -name '*.pdf' -exec sh -c 'exec java -jar jsignpdf-1.4.3/JSignPdf.jar ... ' sh {} +
...and I've tried things like making find output a variable and then using IF THEN to match with no success. Would I need find output to be made into multiple variables? I'm so lost :(
I'd like to accomplish this in some shell, but if there are Perl junkies out there or anything else, I am more than happy for another portable solution.
I've tried to break it down, but still don't understand how to accomplish it...
find files matching VarName without _signed
move _signed file with matching name to the directory of found file
Thanks for any help/guidance.

Use a while loop to read each file found by find and move it to the correct place:
find /some/dir -name "*.pdf" ! -name "*_signed.pdf" -print0 | while IFS= read -d '' -r file
do
f="${file##*/}"
mv "/some/dir/Signed/${f%.*}_signed.pdf" "${file%/*}"
done

I have a similar problem I've been working on. Since the path manipulation required to convert /some/dir/where/is/PDF2.pdf to /some/dir/Signed/PDF2_signed.pdf is fairly simple but more involved than can be done in a simple one-liner, I've been using find to locate the first set, and using a simple loop to process them one at a time. You did mention homework, so I'll try not to give you too much code.
find /dir/containing/unsigned -name '*.pdf' -print0 | while IFS= read -d path; do
fetch_signed_version "$path"
done
where fetch_signed_version is a shell function you write that, given a path such as /some/dir/where/is/PDF2.pdf, extracts the directory (/some/dir/where/is), computes the signed PDF's name (PDF2_signed.pdf), then executes the necessary move (mv /some/dir/Signed/$signed_pdf /some/dir/where/is)
fetch_signed_version is actually pretty simple:
fetch_signed_version () {
dir=${1%/*}
fname=${1##*/}
signed_name=${fname%.pdf}_signed.pdf
mv "/some/dir/Signed/$signed_name" "$dir"
}

Related

script read file contents and copy files

I wrote a script in bash that should read the contents of a text file, look for the corresponding files for each line, and copy them to another folder. It's not copying all the files, only two, the third and the last.
#!/bin/bash
filelist=~/Desktop/file.txt
sourcedir=~/ownCloud2
destdir=~/Desktop/file_out
while read line; do
find $sourcedir -name $line -exec cp '{}' $destdir/$line \;
echo find $sourcedir -name $line
sleep 1
done < "$filelist"
If I use this string on the command line it finds me and copies the file.
find ~/ownCloud2 -name 123456AA.pdf -exec cp '{}' ~/Desktop/file_out/123456AA.pdf \;
If I use the script instead it doesn't work.
I used your exact script and had no problems, for both bash or sh, so maybe you are using another shell in your shebang line.
Use find only when you need to find the file "somewhere" in multiple directories under the search start point.
If you know the exact directory in which the file is located, there is no need to use find. Just use the simple copy command.
Also, if you use "cp -v ..." instead of the "echo", you might see what the command is actually doing, from which you might spot what is wrong.

Check if file is in a folder with a certain name before proceeding

So, I have this simple script which converts videos in a folder into a format which the R4DS can play.
#!/bin/bash
scr='/home/user/dpgv4/dpgv4.py';mkdir -p 'DPG_DS'
find '../Exports' -name "*1080pnornmain.mp4" -exec python3 "$scr" {} \;
The problem is, some of the videos are invalid and won't play, and I've moved those videos to a different directory inside the Exports folder. What I want to do is check to make sure the files are in a folder called new before running the python script on them, preferably within the find command. The path should look something like this:
../Exports/(anything here)/new/*1080pnornmain.mp4
Please note that (anything here) text does not indicate a single directory, it could be something like foo/bar, foo/b/ar, f/o/o/b/a/r, etc.
You cannot use -name because the search is on the path now. My first solution was:
find ./Exports -path '**/new/*1080pnornmain.mp4' -exec python3 "$scr" {} \;
But, as #dan pointed out in the comments, it is wrong because it uses the globstar wildcard (**) unnecessarily:
This checks if /new/ is somewhere in the preceding path, it doesn't have to be a direct parent.
So, the star is not enough here. Another possibility, using find only, could be this one:
find ./Exports -regex '.*/new/[^\/]*1080pnornmain.mp4' -exec python3 "$scr" {} \;
This regex matches:
any number of nested folders before new with .*/new
any character (except / to leave out further subpaths) + your filename with [^\/]*1080pnornmain.mp4
Performances could degrade given that it uses regular expressions.
Generally, instead of using the -exec option of the find command, you should opt to passing each line of find output to xargs because of the more efficient thread spawning, like:
find ./Exports -regex '.*/new/[^\/]*1080pnornmain.mp4' | xargs -0 -I '{}' python3 "$scr" '{}'

Alias for a combination of grep and find is needed

Many times I need to search from a directory and below for a pattern in all files with a specific type. For example, I need to ask grep not to look into files other than *.h, *.cpp or *.c. But if I enter:
grep -r pattern .
it looks into all files. If I enter:
grep -r pattern *.c
it tries *.c files in the current folder (no file in my case) and files in *.c folders (no folder in my case). I want to ask it too look into all folders but only into file with the given type. I think grep is not enough to be used for this purpose. So, I get help from find too, like this:
grep pattern `find . -name '*c'`
First, let me know whether I'm right about getting help from find. Can grep be enough? Second, I prefer to write an alias for bash to be used like this:
mygrep pattern c
to be translated to the same command avoiding usage of ` and ' and be simpler. I tried:
alias mygrep="grep $1 `find . -name '*$2'`"
But it doesn't work and issues an error:
grep: c: No such file or directory
I tried to change it, but I couldn't succeed to a successful alias.
Any idea?
This would be better done as a function than an alias, and using -exec instead of passing the output of find to grep. That output would be subject to word splitting and globbing, so could produce surprising results as is. Instead try:
mygrep () {
find . -name "*$2" -exec grep "$1" {} +
}

Bash. Using find and a for loop. Can't find how to mv file into the directory that was found. Just moves to root directory given.

Current code.
Right now I have a test format set up. Here is what I have.
files=`find C:/PATH/TO/DIRECTORY/2014-05-08 -name *.txt`
(I will be running this from C:/PATH/TO/DIRECTORY. Just right now I'm only testing on todays directory so I don't change everything in /DIRECTORY.) The .sh script is being run from /DIRECTORY therefore it is it's root.) In the implemented version find will look like this:
files=`find C:/PATH/TO/DIRECTORY -name *.txt`
for file in $files; do
#code to manipulate filename
mv $file $newFilename
The problem that I am getting is that this renames the old file name into the new file name, but the new files are in /DIRECTORY when I want them to be in the directory that the program is currently looping through. Which in this case is /DIRECTORY/2014-05-08.
I was just going to make a variable for the current directory of $file, but couldn't figure out how and use:
mv $file /CWD/$newFilename
but I was unable to find a way to assign CWD the directory that $file was in. This would probably be the easiest way if it's possible.
This would be a better way to code that:
find dir -name \*.txt |
while IFS= read -r file; do
dir=$(dirname "$file")
file=$(basename "$file")
newfile=...whatever...
mv "$dir/$file" "$dir/$newfile"
done
Using a for loop over the results of find will iterate over the words in the result, not necessarily the lines -- if there's a filename with a space in it, the for loop won't get the right filename.
The while IFS= read -r line technique I demonstrate is the safest way to iterate over the lines of the result of a command.
There are other issues in my code that I don't think are strictly relevant here.

Bash loop through directory and rename every file

I am horrible at writing bash scripts, but I'm wondering if it's possible to recursively loop through a directory and rename all the files in there by "1.png", "2.png", etc, but I need it to restart at one for every new folder it enters. Here's script that works but only does it for one directory.
cd ./directory
cnt=1
for fname in *
do
mv $fname ${cnt}.png
cnt=$(( $cnt + 1 ))
done
Thanks in advance
EDIT
Can anyone actually write this code out? I have no idea how to write bash, and it's very confusing to me
Using find is a great idea. You can use find with the next syntax to find all directories inside your directory and apply your script to found directories:
find /directory -type d -exec youscript.sh {} \;
-type d parameter means you want to find only directories
-exec youscript.sh {} \; starts your script for every found directory and pass it this directory name as a parameter
Use find(1) to get a list of files, and then do whatever you like with that list.

Resources