output from while read loop using iterative filename/timestamp - bash

I am attempting to create a script that will iteratively run a command against a variable (which is a fully qualified filename) and output the results of that command to an individually named/timestamped file (to %S accuracy). Im not great with this stuff at all
here is what I do:
find /vmfs/volumes/unlistedpathname/unlistedfoldername |
while read list;do
vmkfstools -D "$list" >> duringmigration_10mins_"$list".$(date +"%Y.%m.%d.%H.%M.%S");
done
the output im hoping for is something like
duringmigration_10mins_blahblahblah.vmx.2016.09.25.21.26.35
of course it doesnt work, and im not exactly sure how to solve it. I know the problem outright is $list as the filename variable will reprint the fullpath, so I need some sort of way to tell the loop "hey just use the filename as the variable NOT the full path" but im not sure how to do that in this case. Im also hoping to be able to run this from any location not specific path.

There are two problems preventing the behaviour you are looking for:
As you saw the filenames returned by find include the full path.
Your find command will return all the files and the directory name.
We solve #1 by calling basename on $list in the output filename.
We solve #2 by adding -type f to the find command to only return files and not directories.
find /vmfs/volumes/unlistedpathname/unlistedfoldername -type f |
while read list ; do
vmkfstools -D "${list}" >> "duringmigration_10mins_$(basename "${list}").$(date +"%Y.%m.%d.%H.%M.%S")"
done

Related

How to batch replace part of filenames with the name of their parent directory in a Bash script?

All of my file names follow this pattern:
abc_001.jpg
def_002.jpg
ghi_003.jpg
I want to replace the characters before the numbers and the underscore (not necessarily letters) with the name of the directory in which those files are located. Let's say this directory is called 'Pictures'. So, it would be:
Pictures_001.jpg
Pictures_002.jpg
Pictures_003.jpg
Normally, the way this website works, is that you show what you have done, what problem you have, and we give you a hint on how to solve it. You didn't show us anything, so I will give you a starting point, but not the complete solution.
You need to know what to replace: you have given the examples abc_001 and def_002, are you sure that the length of the "to-be-replaced" part always is equal to 3? In that case, you might use the cut basic command for deleting this. In other ways, you might use the position of the '_' character or you might use grep -o for this matter, like in this simple example:
ls -ltra | grep -o "_[0-9][0-9][0-9].jpg"
As far as the current directory is concerned, you might find this, using the environment variable $PWD (in case Pictures is the deepest subdirectory, you might use cut, using '/' as a separator and take the last found entry).
You can see the current directory with pwd, but alse with echo "${PWD}".
With ${x#something} you can delete something from the beginning of the variable. something can have wildcards, in which case # deletes the smallest, and ## the largest match.
First try the next command for understanding above explanation:
echo "The last part of the current directory `pwd` is ${PWD##*/}"
The same construction can be used for cutting the filename, so you can do
for f in *_*.jpg; do
mv "$f" "${PWD##*/}_${f#*_}"
done

Issue saving result of "find" function in a shell script

I'm pretty new to shell scripting, but it's been great in helping me automating cumbersome tasks in OS X.
One of the functions I'm trying to write in a new script needs to find the specific filename in a subdirectory given a regex string. While I do know that the file exists, the version (and therefore filename itself) is being continually updated.
My function is currently as follows:
fname(){
$2=$(find ./Folder1 -name "$1*NEW*")
}
Which I'm then calling later in my script with the following line:
fname Type1 filename1
What I'm hoping to do is save the filename I'm looking for in variable filename1. My find syntax seems to be correct if I run it in Terminal, but I get the following error when I run my script:
./myscript.sh: line 13: filename1=./Folder1/Type1-list-NEW.bin: No such file or directory
I'm not sure why the result of find is not just saving to the variable I've selected. I'd appreciate any help (regardless of trivial this question may end up being). Thanks!
EDIT: I have a bunch of files in the subdirectory, but with the way I'm setting that folder up I know my "find" query will return exactly 1 filename. I just need the specific filename to do various tasks (updating, version checking, etc.)
The syntax for writing output to a file is command > filename. So it should be:
fname() {
find ./Folder1 -name "$1*NEW*" > "$2"
}
= is for assigning to a variable, not saving output in a file.
Are you sure you need to put the output in a file? Why not put it in a variable:
fname() {
find ./Folder1 -name "$1*NEW*"
}
var=$(fname Type1)
If you really want the function to take the variable name as a parameter, you have to use eval
fname() {
eval "$2='$(find ./Folder1 -name "$1*NEW*")'"
}
Okay, so I'm reading this as, you want to take the output of the find and save it in a shell variable given by $2.
You can't actually use a shell variable to dynamically declare the name of a new shell variable to rename, when the shell sees an expansion at the beginning of a line it immediately begins processing the words as arguments and not as an assignment.
There might be some way of pulling this off with declare and export but generally speaking you don't want to use a shell variable to hold n file names, particularly if you're on OS X, those file names probably have whitespaces and you're not protecting for that in the way your find is outputting.
Generally what you do in this case is you take the list of files find is spitting out and you act on them immediately, either with find -exec or as a part of a find . -print0 | xargs -0 pipeline.

Bash scripting print list of files

Its my first time to use BASH scripting and been looking to some tutorials but cant figure out some codes. I just want to list all the files in a folder, but i cant do it.
Heres my code so far.
#!/bin/bash
# My first script
echo "Printing files..."
FILES="/Bash/sample/*"
for f in $FILES
do
echo "this is $f"
done
and here is my output..
Printing files...
this is /Bash/sample/*
What is wrong with my code?
You misunderstood what bash means by the word "in". The statement for f in $FILES simply iterates over (space-delimited) words in the string $FILES, whose value is "/Bash/sample" (one word). You seemingly want the files that are "in" the named directory, a spatial metaphor that bash's syntax doesn't assume, so you would have to explicitly tell it to list the files.
for f in `ls $FILES` # illustrates the problem - but don't actually do this (see below)
...
might do it. This converts the output of the ls command into a string, "in" which there will be one word per file.
NB: this example is to help understand what "in" means but is not a good general solution. It will run into trouble as soon as one of the files has a space in its nameā€”such files will contribute two or more words to the list, each of which taken alone may not be a valid filename. This highlights (a) that you should always take extra steps to program around the whitespace problem in bash and similar shells, and (b) that you should avoid spaces in your own file and directory names, because you'll come across plenty of otherwise useful third-party scripts and utilities that have not made the effort to comply with (a). Unfortunately, proper compliance can often lead to quite obfuscated syntax in bash.
I think problem in path "/Bash/sample/*".
U need change this location to absolute, for example:
/home/username/Bash/sample/*
Or use relative path, for example:
~/Bash/sample/*
On most systems this is fully equivalent for:
/home/username/Bash/sample/*
Where username is your current username, use whoami to see your current username.
Best place for learning Bash: http://www.tldp.org/LDP/abs/html/index.html
This should work:
echo "Printing files..."
FILES=(/Bash/sample/*) # create an array.
# Works with filenames containing spaces.
# String variable does not work for that case.
for f in "${FILES[#]}" # iterate over the array.
do
echo "this is $f"
done
& you should not parse ls output.
Take a list of your files)
If you want to take list of your files and see them:
ls ###Takes list###
ls -sh ###Takes list + File size###
...
If you want to send list of files to a file to read and check them later:
ls > FileName.Format ###Takes list and sends them to a file###
ls > FileName.Format ###Takes list with file size and sends them to a file###

Recursively dumping the content of a file located in different folders

Still being a newbie with bash-programming I am fighting with another task I got. A specific file called ".dump" (yes, with a dot in the beginning) is located in each folder and always contains three numbers. I need to dump the third number in a variable in case it is greater than 1000 and then print this and the folder name locating the number. So the outcome should look like this:
"/dir1/ 1245"
"/dir1/subdir1/ 3434"
"/dir1/subdir2/ 10003"
"/dir1/subdir2/subsubdir3/ 4123"
"/dir2/ 45440"
(without "" and each of them in a new line (not sure, why it is not shown correctly here))
I was playing around with awk, find and while, but the results are that bad that I do not wanna post them here, which I hope is understood. So any code snippet helping is appreciated.
This could be cleaned up, but should work:
find /dir1 /dir2 -name .dump -exec sh -c 'k=$(awk "\$3 > 1000{print \$3; exit 1}" $0) ||
echo ${0%.dump} $k ' {} \;
(I'm assuming that all three numbers in your .dump files appear on one line. The awk will need to be modified if the input is in a different format.)

Organized logging in Bash

I have this script that deletes files 7 days or older and then logs them to a folder. It logs and deletes everything correctly but when I open up the log file for viewing, its very sloppy.
log=$HOME/Deleted/$(date)
find $HOME/OldLogFiles/ -type f -mtime +7 -delete -print > "$log"
The log file is difficult to read
Example File Output: (when opened in notepad)
/home/u0146121/OldLogFiles/file1.txt/home/u0146121/OldLogFiles/file2.txt/home/u0146121/OldLogFiles/file3.txt
Is there anyway to log the file nicer and cleaner? Maybe with the Filename, date deleted, and how old it was?
try
log=$HOME/Deleted/$(date);
# change it to echo -e and insert new line if needed
find $HOME/OldLogFiles/ -type f -mtime +7 -exec echo {} \; > "$log"
Use logger for logging and leave the actual log handling to syslog and logrotate.
In you deletion script you can add 2 variables at the top, lets call them "timeDate" and "logDestination". They would look like this:
timeDate=$(date "+%d/%m/%Y %T")
logDestination=/home/$USER/.deleteLog; touch $logDestination
Now the $(date "+%d/%m/%Y %T") part simply gets the current date and time. It goes off to get the system date as; date (+%d) then the month and year (%m) (%Y) b.t.w the capital Y returns a full year as in YYYY. It then stores that date and time in the variable for later use.
The logDestination variable is holding a directory or file path for us, pointing at a file called .deleteLog . Now the touch part that follows isn't entirely necessary but it will make the file exist if it does not exist or has been deleted or renamed by accident.
In bash scripting and in a host of programming languages one makes methods or functions, which are just simply sections of code that usually do just one job. This function below is designed to write a message to a logfile:
## Logging function
function _logger () {
echo -e "$timeDate $user - $#\r" >> $log
}
A simply explanation of the function is it is able to receive a form of information. If you look at the above function, note the "$#" symbol this is a placeholder if you like, it will put any and all strings (text) you point at the function. Here is a more cut down version of a function that can receive several strings or input:
function _example () {
echo -e "$#"
}
To call this function and give it a message we can literally type (anywhere under the function in your script):
example "hello"
This string "hello" is given to the function and the echo line in the function outputs the "hello" to your terminal or screen. The -e in the echo line helps the echo to distinguish active parts, Tho best use "man echo" to give you a understanding of its behavior modification.
So back to your script.
Lets say you have a line that deletes the contents of a directory (I advise care and caution and discourage such line but meh).
after it has done a delete you can call;
_logger "deletion of file (or $FILE) successful."
and the _logger function will nicely place the date time, message and start a new line for you (\r). The $user in the function is placed by your current view. as in it will say your username.
Functions can be called numerous times, saving you code duplication and makes the scripts look neater.
You're asking a few things here...
Why does my log file look funny in notepad?
Probably because of the tool you're using to view it, but it's hard to say without more information. Notepad is a Windows application, and Windows uses a strategy than unix for ending lines in a text file. If you want to know for sure, use a tool like hexdump or od to see what's really going on inside your file. And as was suggested in comments, head filename.log on the unix host may show you what you expect, in which case you will have confirmed the line ending theory.
If Notepad is your tool of choice, then we can show you how to convert the files so that they're more compatible with Windows. Otherwise, let us know what you really need.
How do I include this extra data in my log?
You're asking for a few items:
filename - easy enough, you've already got that.
date deleted - easy enough, you just need a timestamp for each of the entries.
age of file - harder. Will the timestamp of the file be sufficient, or do you want, for example, the date extracted from the first line of the file itself? If the latter, you'll need to include a sample in your question. I'll go with the former for now.
First off, bash version 4 and up lets you use time formats as part of a printf, so you could do the following:
printf -v log '%s/Deleted/%(%F)T' "$HOME"
But honestly, I think this was a hack, and what you really want is a single text file that gets rotated using some other tool.
log=/var/log/whatever.log
The "some other tool" will depend on your operating system. In FreeBSD, look in /etc/newsyslog.conf. In Linux, refer to the documentation for your distribution, looking for things like "logrotate".
The output from your find command can be the files which were successfully deleted, but you don't have to have just filenames. Consider the following:
find "$HOME/OldLogFiles/" -type f -mtime +7 -ls -delete
The output of this is the entire ls line, before the file is deleted. It includes the file's date and inode number. Preface this with a timestamp, and you may be good to go:
find "$HOME/OldLogFiles/" -type f -mtime +7 -ls -delete | sed "s/^/[$(printf '%(%F %T)T')] /"
Or if you prefer to do this without sed:
while read line; do
printf '[%(%F %T)T] %s\n' -1 "$line"
done < <(find "$HOME/OldLogFiles/" -type f -mtime +7 -ls -delete)
In this version, the -1 is interpreted by printf's %T as "now".
Redirect to $log as you like.
Finally, and this is probably the "best" option, is to avoid managing logfiles in bash entirely, and use your system's logger command instead. The POSIX version is pretty bare-bones, but you can control things syslog style in BSD, Linux, etc.

Resources