Looping through files in different directory given command line argument - linux

I'm trying to extend a script that implements something like a recycling bin for files on Linux. I have the code that I'm extending at the bottom.
In my extension, when the script is presented with the command line argument -cleanup I want to loop through files that are in the /home/7/bearm/.garbage directory, and have the user decide whether they want to delete the file or not.
However, I don't know how to detect when the command line argument is there. The command line can have other parameters, I just want to loop through the files when -cleanup is used.
I also do not know how to loop through files that are in a different directory (/home/7/bearm/.garbage).
How would I go around doing these things?
set directory = '/home/7/bearm/.garbage/'
if(! -d "$directory") then
mkdir .garbage
mv .garbage /home/7/bearm/
endif
set n = 1
while ($n <= $#argv)
set file = $argv[$n]
if(-d $file) then
#do nothing
echo "Cannot trash directory $file"
else
mv $file /home/7/bearm/.garbage
echo "Trashed $file"
endif
# n++
end
du -h /home/7/bearm/.garbage

To test if arguments contains -cleanup, you can do that (tested with ash on Minix3):
if echo "$#" | grep -- "-cleanup" >/dev/null 2>&1; then
echo "-cleanup is present..."
fi
Moreover, if you want a proper solution to use long GNU style options, see http://www.sputnick-area.net/scripts/getopts_long_example.sh and http://www.sputnick-area.net/scripts/getopts_long.sh
A bash version of your pseudo script :
#!/bin/bash
directory='/home/7/bearm/.garbage/'
mkdir -p "$directory"
for arg; do
if [[ -d $arg ]]; then
#do nothing
echo "Cannot trash directory $arg" >&2
else
mv "$arg" "$directory"
echo "Trashed $arg"
fi
done
du -sh "$directory"
Feel free to improve it with -cleanup switch.

Related

How can I remove the extension of specific files in a directory?

I want to remove the extension of specific files with a given extension.
So for instance, in a directory foobar, we have foo.txt, bar.txt foobar.jpg.
Additionally, the extension that I've put in to be removed is txt
After calling the program, my output should be foo bar foobar.jpg
Here is my code so far:
#!/bin/bash
echo "Enter an extension"
read extension
echo "Enter a directory"
read directory
for file in "$directory"/*; do //
if [[ $file == *.txt ]]
then
echo "${file%.*}"
else
echo "$file"
fi
done
However when I run this on a given directory, nothing shows up.
I'm assuming that there is a problem with how I referred to the directory ( in the line where I placed a //) and I've tried to research on how to solve it but to no avail.
What am I doing wrong?
If files do exist in a valid directory you've entered then they should show up — with one exception. If you are using ~/ (shorthand home directory) then it will be treated as plain text in your for loop. The read variable should be substituted into another variable so the for loop can treat it as a directory (absolute paths should work normally as well).
#!/bin/bash
echo "Enter an extension"
read -r extension
echo "Enter a directory"
read -r directory
dir="${directory/#\~/$HOME}"
for file in "$dir"/*; do
if [[ $file == *."$extension" ]]
then
echo "${file%.*}"
else
echo "$file"
fi
done
You can simplify your for-loop:
for file in "$directory"/*; do
echo "${f%.$extension}";
done
The % instructions removes only matching characters. If nothing matches, the original string (here f) is returned.
When you write bash scripts it's more common to pass arguments to your script via command line arguments rather than by reading it from standard input via read program.
Passing arguments via command line:
#!/bin/bash
# $# - a bash variable which holds a number of arguments passed
# to script via command line arguments
# $0 holds the name of the script
if [[ $# -ne 2 ]]; then # checks if exactly 2 arguments were passed to script
echo "Usage: $0 EXTENSION DIRECTORY"
exit -1;
fi
echo $1; # first argument passed to script
echo $2; # second arugment passed to script
This approach is more efficient because a subprocess is spawn for read command to run and there is no subprocess spawn for reading command line arguments.
There is no need to manually loop through directory, you can use find command to find all files with given extension within given directory.
find /path/to/my/dir -name '*.txt'
find $DIRECTORY -name "*.$EXTENSION"
# note that single quotes in this context would prevent $EXTENSION
# variable to be resolved, so double quotes are used " "
# find searches for files inside $DIRECTORY and searches for files
# matching pattern '*.$EXTENSION'
Note that to avoid bash filename expansion sometimes it is required to wrap actual pattern in single quotes ' ' or double quotes " ". See Bash Filename Expansion
So now your script can look like this:
#!/bin/bash
if [[ $# -ne 2 ]]; then
echo "Usage: $0 EXTENSION DIRECTORY"
exit -1;
fi
$EXTENSION = $1 # for better readability
$DIRECTORY = $2
for file in `find $DIRECTORY -name "*.$EXTENSION"`; do
mv $file ${file%.$EXTENSION}
done
Construct ${file%.$EXTENSION} is called Shell Parameter Expansion it searches for occurrence of .$EXTENSION inside file variable and deletes it.
Notice that in the script it is easy to pass extension as directory and vice versa.
We can check if second argument is in fact directory, we can use following construction:
if ! [[ -d $DIRECTORY ]]; then
echo $DIRECTORY is not a dir
exit -1
fi
This way we can exit from the script earlier with more readable error.
To sum up entire script could look like this:
#!/bin/bash
if [[ $# -ne 2 ]]; then
echo "Usage: $0 EXTENSION DIRECTORY"
exit -1;
fi
EXTENSION=$1 # for better readability
DIRECTORY=$2
if ! [[ -d $DIRECTORY ]]; then
echo $DIRECTORY is not a directory.
exit -1
fi
for file in `find $DIRECTORY -name "*.$EXTENSION"`; do
mv $file ${file%.$EXTENSION}
done
Example usage:
$ ./my-script.sh txt /path/to/directory/with/files

How to extract only file name return from diff command?

I am trying to prepare a bash script for sync 2 directories. But I am not able to file name return from diff. everytime it converts to array.
Here is my code :
#!/bin/bash
DIRS1=`diff -r /opt/lampp/htdocs/scripts/dev/ /opt/lampp/htdocs/scripts/www/ `
for DIR in $DIRS1
do
echo $DIR
done
And if I run this script I get out put something like this :
Only
in
/opt/lampp/htdocs/scripts/www/:
file1
diff
-r
"/opt/lampp/htdocs/scripts/dev/File
1.txt"
"/opt/lampp/htdocs/scripts/www/File
1.txt"
0a1
>
sa
das
Only
in
/opt/lampp/htdocs/scripts/www/:
File
1.txt~
Only
in
/opt/lampp/htdocs/scripts/www/:
file
2
-
second
Actually I just want to file name where I find the diffrence so I can take perticular action either copy/delete.
Thanks
I don't think diff produces output which can be parsed easily for your purposes. It's possible to solve your problem by iterating over the files in the two directories and running diff on them, using the return value from diff instead (and throwing the diff output away).
The code to do this is a bit long, but here it is:
DIR1=./one # set as required
DIR2=./two # set as required
# Process any files in $DIR1 only, or in both $DIR1 and $DIR2
find $DIR1 -type f -print0 | while read -d $'\0' -r file1; do
relative_path=${file1#${DIR1}/};
file2="$DIR2/$relative_path"
if [[ ! -f "$file2" ]]; then
echo "'$relative_path' in '$DIR1' only"
# Do more stuff here
elif diff -q "$file1" "$file2" >/dev/null; then
echo "'$relative_path' same in '$DIR1' and '$DIR2'"
# Do more stuff here
else
echo "'$relative_path' different between '$DIR1' and '$DIR2'"
# Do more stuff here
fi
done
# Process files in $DIR2 only
find $DIR2 -type f -print0 | while read -d $'\0' -r file2; do
relative_path=${file2#${DIR2}/};
file1="$DIR1/$relative_path"
if [[ ! -f "$file2" ]]; then
echo "'$relative_path' in '$DIR2 only'"
# Do more stuff here
fi
done
This code leverages some tricks to safely handle files which contain spaces, which would be very difficult to get working by parsing diff output. You can find more details on that topic here.
Of course this doesn't do anything regarding files which have the same contents but different names or are located in different directories.
I tested by populating two test directories as follows:
echo "dir one only" > "$DIR1/dir one only.txt"
echo "dir two only" > "$DIR2/dir two only.txt"
echo "in both, same" > $DIR1/"in both, same.txt"
echo "in both, same" > $DIR2/"in both, same.txt"
echo "in both, and different" > $DIR1/"in both, different.txt"
echo "in both, but different" > $DIR2/"in both, different.txt"
My output was:
'dir one only.txt' in './one' only
'in both, different.txt' different between './one' and './two'
'in both, same.txt' same in './one' and './two'
Use -q flag and avoid the for loop:
diff -rq /opt/lampp/htdocs/scripts/dev/ /opt/lampp/htdocs/scripts/www/
If you only want the files that differs:
diff -rq /opt/lampp/htdocs/scripts/dev/ /opt/lampp/htdocs/scripts/www/ |grep -Po '(?<=Files )\w+'|while read file; do
echo $file
done
-q --brief
Output only whether files differ.
But defitnitely you should check rsync: http://linux.die.net/man/1/rsync

linux zip and exclude dir via bash/shell script

I am trying to write a bash/shell script to zip up a specific folder and ignore certain sub-dirs in that folder.
This is the folder I am trying to zip "sync_test5":
My bash script generates an ignore list (based on) and calls the zip function like this:
#!/bin/bash
SYNC_WEB_ROOT_BASE_DIR="/home/www-data/public_html"
SYNC_WEB_ROOT_BACKUP_DIR="sync_test5"
SYNC_WEB_ROOT_IGNORE_DIR="dir_to_ignore dir2_to_ignore"
ignorelist=""
if [ "$SYNC_WEB_ROOT_IGNORE_DIR" != "" ];
then
for ignoredir in $SYNC_WEB_ROOT_IGNORE_DIR
do
ignorelist="$ignorelist $SYNC_WEB_ROOT_BACKUP_DIR/$ignoredir/**\*"
done
fi
FILE="$SYNC_BACKUP_DIR/$DATETIMENOW.website.zip"
cd $SYNC_WEB_ROOT_BASE_DIR;
zip -r $FILE $SYNC_WEB_ROOT_BACKUP_DIR -x $ignorelist >/dev/null
echo "Done"
Now this script runs without error, however it is not ignoring/excluding the dirs I've specified.
So, I had the shell script output the command it tried to run, which was:
zip -r 12-08-2014_072810.website.zip sync_test5 -x sync_test5/dir_to_ignore/**\* sync_test5/dir2_to_ignore/**\*
Now If I run the above command directly in putty like this, it works:
So, why doesn't my shell script exclude working as intended? the command that is being executed is identical (in shell and putty directly).
Because backslash quotings in a variable after word splitting are not evaluated.
If you have a='123\4', echo $a would give
123\4
But if you do it directly like echo 123\4, you'd get
1234
Clearly the arguments you pass with the variable and without the variables are different.
You probably just meant to not quote your argument with backslash:
ignorelist="$ignorelist $SYNC_WEB_ROOT_BACKUP_DIR/$ignoredir/***"
Btw, what actual works is a non-evaluated glob pattern:
zip -r 12-08-2014_072810.website.zip sync_test5 -x 'sync_test5/dir_to_ignore/***' 'sync_test5/dir2_to_ignore/***'
You can verify this with
echo zip -r 12-08-2014_072810.website.zip sync_test5 -x sync_test5/dir_to_ignore/**\* sync_test5/dir2_to_ignore/**\*
And this is my suggestion:
#!/bin/bash
SYNC_WEB_ROOT_BASE_DIR="/home/www-data/public_html"
SYNC_WEB_ROOT_BACKUP_DIR="sync_test5"
SYNC_WEB_ROOT_IGNORE_DIR=("dir_to_ignore" "dir2_to_ignore")
IGNORE_LIST=()
if [[ -n $SYNC_WEB_ROOT_IGNORE_DIR ]]; then
for IGNORE_DIR in "${SYNC_WEB_ROOT_IGNORE_DIR[#]}"; do
IGNORE_LIST+=("$SYNC_WEB_ROOT_BACKUP_DIR/$IGNORE_DIR/***") ## "$SYNC_WEB_ROOT_BACKUP_DIR/$IGNORE_DIR/*" perhaps is enough?
done
fi
FILE="$SYNC_BACKUP_DIR/$DATETIMENOW.website.zip" ## Where is $SYNC_BACKUP_DIR set?
cd "$SYNC_WEB_ROOT_BASE_DIR";
zip -r "$FILE" "$SYNC_WEB_ROOT_BACKUP_DIR" -x "${IGNORE_LIST[#]}" >/dev/null
echo "Done"
This is what I ended up with:
#!/bin/bash
# This script zips a directory, excluding specified files, types and subdirectories.
# while zipping the directory it excludes hidden directories and certain file types
[[ "`/usr/bin/tty`" == "not a tty" ]] && . ~/.bash_profile
DIRECTORY=$(cd `dirname $0` && pwd)
if [[ -z $1 ]]; then
echo "Usage: managed_directory_compressor /your-directory/ zip-file-name"
else
DIRECTORY_TO_COMPRESS=${1%/}
ZIPPED_FILE="$2.zip"
COMPRESS_IGNORE_FILE=("\.git" "*.zip" "*.csv" "*.json" "gulpfile.js" "*.rb" "*.bak" "*.swp" "*.back" "*.merge" "*.txt" "*.sh" "bower_components" "node_modules")
COMPRESS_IGNORE_DIR=("bower_components" "node_modules")
IGNORE_LIST=("*/\.*" "\.* "\/\.*"")
if [[ -n $COMPRESS_IGNORE_FILE ]]; then
for IGNORE_FILES in "${COMPRESS_IGNORE_FILE[#]}"; do
IGNORE_LIST+=("$DIRECTORY_TO_COMPRESS/$IGNORE_FILES/*")
done
for IGNORE_DIR in "${COMPRESS_IGNORE_DIR[#]}"; do
IGNORE_LIST+=("$DIRECTORY_TO_COMPRESS/$IGNORE_DIR/")
done
fi
zip -r "$ZIPPED_FILE" "$DIRECTORY_TO_COMPRESS" -x "${IGNORE_LIST[#]}" # >/dev/null
# echo zip -r "$ZIPPED_FILE" "$DIRECTORY_TO_COMPRESS" -x "${IGNORE_LIST[#]}" # >/dev/null
echo $DIRECTORY_TO_COMPRESS "compressed as" $ZIPPED_FILE.
fi
After a few trial and error, I have managed to fix this problem by changing this line:
ignorelist="$ignorelist $SYNC_WEB_ROOT_BACKUP_DIR/$ignoredir/**\*"
to:
ignorelist="$ignorelist $SYNC_WEB_ROOT_BACKUP_DIR/$ignoredir/***"
Not sure why this worked, but it does :)

Shell Scripting: Print directory names and files with specifics

In my script, I am asking the user to input a directory and then list all the files in that specific directory. What I want to do with that is to make the display a little better in which I would be able to display a "/" if the item in the directory is another directory and if it is an executable file (not an executable directory), print with a **".
This is what I have:
echo “Directory: “
read thing
for var123 in $thing*
do
echo $var123
done
In a directory I have a few folders and a few scripts that have the execute permission. when I run the script I want to say
/folder1/subfolder1/
/folder1/subfolder2/
/folder1/file1*
/folder1/file2*
I am new to this and have no clue what I am doing. Any help will be greatly appreciated.
You might want to check and make sure the user inputs something that ends in a / first.
e.g.
[[ $thing =~ '/'$ ]] || thing="$thing/"
Also check if it exists
e.g.
[[ -d $thing ]] || exit 1
Then for checking if it's a directory use the -d test as above. To check if executable file use -x. So putting that all together, try:
#!/bin/bash
echo “Directory: “
read thing
[[ $thing =~ '/'$ ]] || thing="$thing/"
[[ -d $thing ]] || exit 1
for var123 in "$thing"*
do
if [[ -f $var123 && -x $var123 ]]; then
echo "$var123**"
elif [[ -d $var123 ]]; then
echo "$var123/"
else
echo "$var123"
fi
done
ls -F is your friend here - if you want to do it for the current directory:
ls -F
If you want to do it for all files & subfolders of the current directory:
find * -exec ls -Fd {} \;
... and for a given directory:
echo "Directory: "
read DIR
find $DIR/* -exec ls -Fd {} \;
Edit: ls -F will append a / to directories and a * to executables. If you want ** instead, just use sed to replace them:
find $DIR/* -exec ls -Fd {} \; | sed 's/\*$/&&/'
And this approach works in all shells, not just bash.

Bash: Create a file if it does not exist, otherwise check to see if it is writeable

I have a bash program that will write to an output file. This file may or may not exist, but the script must check permissions and fail early. I can't find an elegant way to make this happen. Here's what I have tried.
set +e
touch $file
set -e
if [ $? -ne 0 ]; then exit;fi
I keep set -e on for this script so it fails if there is ever an error on any line. Is there an easier way to do the above script?
Why complicate things?
file=exists_and_writeable
if [ ! -e "$file" ] ; then
touch "$file"
fi
if [ ! -w "$file" ] ; then
echo cannot write to $file
exit 1
fi
Or, more concisely,
( [ -e "$file" ] || touch "$file" ) && [ ! -w "$file" ] && echo cannot write to $file && exit 1
Rather than check $? on a different line, check the return value immediately like this:
touch file || exit
As long as your umask doesn't restrict the write bit from being set, you can just rely on the return value of touch
You can use -w to check if a file is writable (search for it in the bash man page).
if [[ ! -w $file ]]; then exit; fi
Why must the script fail early? By separating the writable test and the file open() you introduce a race condition. Instead, why not try to open (truncate/append) the file for writing, and deal with the error if it occurs? Something like:
$ echo foo > output.txt
$ if [ $? -ne 0 ]; then die("Couldn't echo foo")
As others mention, the "noclobber" option might be useful if you want to avoid overwriting existing files.
Open the file for writing. In the shell, this is done with an output redirection. You can redirect the shell's standard output by putting the redirection on the exec built-in with no argument.
set -e
exec >shell.out # exit if shell.out can't be opened
echo "This will appear in shell.out"
Make sure you haven't set the noclobber option (which is useful interactively but often unusable in scripts). Use > if you want to truncate the file if it exists, and >> if you want to append instead.
If you only want to test permissions, you can run : >foo.out to create the file (or truncate it if it exists).
If you only want some commands to write to the file, open it on some other descriptor, then redirect as needed.
set -e
exec 3>foo.out
echo "This will appear on the standard output"
echo >&3 "This will appear in foo.out"
echo "This will appear both on standard output and in foo.out" | tee /dev/fd/3
(/dev/fd is not supported everywhere; it's available at least on Linux, *BSD, Solaris and Cygwin.)

Resources