Shell Scripting to Compress directory [duplicate] - linux

This question already has an answer here:
Shell spacing in square brackets [duplicate]
(1 answer)
Closed 4 years ago.
$1 is file / folder that want to compressed
Output filename is the same name, plus current date and ext
if output name exist, then just give warning
Example:
./cmp.sh /home/user
It will be /home/user to /home/user_2018-03-11.tar.bz2
i already have lead, but i'm stuck
#!/bin/bash
if ["$1" == ""]; then
echo "Help : To compress file use argument with directory"
exit 0
fi
if [[ -f "$1" || -d "$1" ]]; then
tar -cvjSf $1"_"$(date '+%d-%m-%y').tar.bz2 $1
fi
but the output is _22-04-2018.tar.bz2

I see that you're using quotes to avoid the problem the underscore getting used as part of the variable name. So while $1 is a positional paramater, $1_ is a variable that you have not set in your script. You can avoid this issue by using curly braces, like ${1}. Anything inside the braces is part of the variable name, so ${1}_ works. This notation would be preferable to $1"_" which leaves a user-provided variable outside of quotes. (Of course, "$1"_ would do the job as wel.)
Also, it's probably safer to set the filename in a variable, then use that for all your needs:
#!/bin/bash
if [ -z "$1" ]; then
echo "Help : To compress file use argument with directory"
exit 0
fi
filename="${1}_$(date '+%F').tar.bz2"
if [ -e "$filename" ]; then
echo "WARNING: file exists: $filename" >&2
else
tar -cvjSf "$filename" "$#"
fi
Changes:
you need spaces around your square brackets in an if condition,
while you can test for equivalence to a null string, -z is cleaner, though you could also test for [ $# -eq 0 ], counting the parameters provided,
using $filename makes sure that your test and your tar will always use the same name, even if the script runs over midnight, and is way more readable,
variables should always be quoted.
Also, are you sure about the -S option for tar? On my system, that option extracts sparse files, and is only useful in conjunction with -x.
ALSO, I should note that as I've rewritten it, there's nothing in this script which is specific to bash, and it should be portable to POSIX shells as well (ash/dash/etc). Bash is great, but it's not universal, and if through your learning journey you can learn both, it will give you useful skills across multiple operating systems and environments.

Use -z switch to check if blank
#!/bin/bash
if [[ -z "$1" ]]; then
echo "Help : To compress file use argument with directory"
exit 0
fi
if [[ -f "$1" || -d "$1" ]]; then
tar -cvjSf $1"_"$(date '+%d-%m-%y').tar.bz2 $1
fi

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 write a bash shell script which takes one argument (directory name)

How to write a bash shell script called 'abc' which takes one argument, the name of a directory, and adds the extension ".xyz" to all visible files in the directory that don't already have it
I have mostly written the code which changes the filenames inside the current directory but I can't get the script to accept an argument (directory name) and change the filenames of that directory
#!/bin/bash
case $# in
0) echo "No directory name provided" >&2 ; exit 1;;
1) cd "${1}" || exit $?;;
*) echo "Too many parameters provided" >&2 ; exit 1;;
esac
for filename in *
do
echo $filename | grep "\.xyz$"
if [ "$?" -ne "0" ]
then mv "$filename" "$filename.old"
fi
done
additional instructions include;
Within 'abc', use a "for" control structure to loop through all the non-hidden filenames
in the directory name in $1. Also, use command substitution
with "ls $1" instead of an ambiguous filename, or you'll descend into subdirectories.
EDIT: The top part of the question has been answered below, however the second part requires me to modify my own code according to the following instructions:
Modify the command substitution that's being used to create the loop values that will be placed into the "filename" variable. Instead of just an "ls $1", pipe the output into a "grep". The "grep" will search for all filenames that DO NOT end in ".xyz". This can easily be done with the "grep -v" option. With this approach, you can get rid of the "echo ... | grep ..." and the "if" control structure inside the loop, and simply do the rename.
How would I go about achieving this because according to my understanding, the answer below is already only searching through filenames without the .xyz extension however it is not being accepted.
Your description is a little unclear in places, so I've gone with the most obvious:
#!/bin/bash
# validate input parameters
case $# in
0) echo "No directory name provided" >&2 ; exit 1;;
1) cd "${1}" || exit $?;;
*) echo "Too many parameters provided" >&2 ; exit 1;;
esac
shopt -s extglob # Enables extended globbing
# Search for files that do not end with .xyz and rename them (needs extended globbing for 'not match')
for filename in !(*.xyz)
do
test -f && mv "${filename}" "${filename}.xyz"
done
The answer to the second part is this:
#!/bin/bash
for file in $(ls -1 "$1" | grep -v '\.old$'); do
mv "$file" "$file.old"
done
I got it from somewhere

Copy multiple files with bash script from command line arguments?

I want to create a script that allows me to enter multiple filenames from the command line, and have the script copy those files to another directory. This is what I am trying but I keep getting an error of
line 10: binary operator expected
#!/bin/bash
DIRECTORY=/.test_files
FILE=$*
if [ -e $DIRECTORY/$FILE ]; then
echo "File already exists"
else
cp $FILE $DIRECTORY
fi
So if the script was named copfiles.sh, I am writing...
./copyfiles.sh doc1.txt doc2.txt
It will move the files, but if they already exist, it won't read the error message.
Also I get the "line 10: binary operator expected" error regardless of it the files are there or not. Can anyone tell me what I am doing wrong?
As a possible problem, if you had a filename with a space or had multiple arguments $* would have spaces in it so [ -e $DIR/$FILE ] will expand to have lots of words, like [ -e /.test_files/First word and more ] and -e expects just 1 word after it. Try putting it in quotes like
if [ -e "$DIRECTORY/$FILE" ]
Of course, you may only want to store $1 in $FILE to get just the first argument.
To test all the arguments you want to loop over the arguments and test each with something like
for FILE in "$#"; do
if [ -e "$DIRECTORY/$FILE" ]; then
echo "$FILE already exists"
else
cp "$FILE" $DIRECTORY
fi
done
Using quotes around $# to preserve spaces in the original arguments as well

What is the error in this shell script

I never used shell script, but now I have to , here is what I'm trying to do :
#!/bin/bash
echo running the program
./first
var = ($(ls FODLDER |wc -l)) #check how many files the folder contains
echo $var
if( ["$var" -gt "2"] #check if there are more the 2file
then ./second
fi
the scriopt crashes at the if statement. how may I solve this
Many:
var = ($(ls FODLDER |wc -l))
This is wrong, you cannot have those spaces around =.
if( ["$var" -gt "2"]
Your ( is not doing anything there, so it has to be deleted. Also, you need spaces around [ and ].
All together, this would make more sense:
#!/bin/bash
echo "running the program"
./first
var=$(find FOLDER -maxdepth 1 -type f|wc -l) # better find than ls
echo "$var"
if [ "$var" -gt "2" ]; then
./second
fi
Note:
quote whenever you echo, specially when handling variables.
see another way to look for files in a given path. Parsing ls is kind of evil.
indent your code for better readibility.
Edit your script.bash file as follow:
#!/bin/env bash
dir="$1"
echo "running the program"
./first
dir_list=( $dir/* ) # list files in directory
echo ${#dir_list[#]} # count files in array
if (( ${#dir_list[#]} > 2 )); then # test how many files
./second
fi
Usage
script.bash /tmp/
Explaination
You need to learn bash to avoid dangerous actions!
pass the directory to work with as first argument in the command line (/tmp/ → `$1)
use glob to create an array (dir_list) containing all file in given directory
count items in array (${#dir_list[#]})
test the number of item using arithmetic context.

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