How do I find out if a parameter contains a "/" and if it does create the directory before it and the file after it - linux

I am trying to create a script that takes a parameter with a / in the between the directory and file. I then want to create the directory and create the file inside that directory. I don't really have a huge idea of what I am doing so I don't have any code other than the basic skeleton for a bash if statement.
#!/bin/bash
if [ $1 ?? "/" ]; then
do
fi
If for example the parameter of Website/Google is passed a directory called Website should be created with a file called Google inside it.

if [[ "$1" = */* ]];
dir=${1#/*}
file=${1%%*/}
fi
in bash, or more generally for a POSIX-compatible shell,
case $1 in
*/*) dir=${1#/*}; file=${1%%*/} ;;
esac

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

Shell Scripting to Compress directory [duplicate]

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

'Housekeeping' script to create folder and move files

I have been trying to investigate how to automate a script (shell or other) that periodically (once an hour for example) moves files with a constant naming convention to a folder (which is automatically created if not already there).
The files are like this:
Camera1_01_20171213221830928.jpg
Camera1_01_20171213223142881.mp4
Basically it will be doing 'housekeeping'.
I'm new to shell scripts, and I just can't work out how to create a folder if it is not there (folder called 20171213 for example), then move the relevant files into it?
Any help would be greatly appreciated.
You can use if [ ! -d "$DIRNAME ]" to see if a directory called $DIRNAME exists. Further you can use mkdir to create directories.
Alternatively, you can just use mkdir and ignore any error that a directory already exists. Other than that, there are no negative side effects.
I won't be able to write the script for you because you don't have enough specific information in your post, but here are the tools I think you'll need:
Bash - Bash, for writing your script (Here's another useful reference)
Mkdir - For making a directory if its missing (look at the -p flag).
Cron - For scheduling the hourly execution of your script.
Sed - For creating the new file names from the old ones
The basic code will look something like this:
#!/bin/bash
FILES=/home/joshua/photos/sort-me
for photo in $FILES/*.jpg; do
if [[ -f photo ]]; then
new_photo_location= #<figure this part out based on your needs>
echo "I would move $photo to $new_photo_location"
#mv $photo $new_photo_location
fi
done
I recommend running it with that echo only until you see exactly what you like, then comment that line out and uncommont the mv line.
Finally, to run the script every hour on the hour, your cron entry will look something like this. (Type crontab -e to edit your crontab):
# Min Hour Day Month Day-of-Wk Year Must use absolute path
0 * * * * * /home/joshua/bin/sort-photos.sh
VDIR=$( date +%F )
VFILESFX=Camera*
[ ! -d $VDIR ] && mkdir $VDIR
while true ; do
LTMP=$( ls $VFILESFX )
for i in $LTMP ; do
fuser ${i}
[ $? -ne 0 ] && mv $i $VDIR/;
echo "File ${i} moved to ${VDIR}";
done
sleep 3600
done
Just some explanation about this script...
The first part set the current date into a variable VDIR and the prefix Camera* into VFILESFX.
The conditional command create a directory with the value existent in VDIR if this directory does not exist, after that go to a while command interacting every hour listing all the file with the prefix setted into VFILESFX and moving this files to the directory with the current date
I finally went with a perl script which I could more easily trigger from a cron job:
#!/usr/bin/perl -w
use strict;
use Data::Dumper;
use File::Copy;
main();
sub main
{
my $dir = "/srv/NAS1/Camera1";
opendir(my $fh, $dir) or die("Could not open '$dir' for reading: $!\n");
my #files = readdir($fh);
closedir($fh);
foreach my $file(#files)
{
if(-d $file)
{
next; # skip file if its a folder
}
if($file =~ /Camera1_01_(\d{8})\d{9}\.(jpg|mp4)/)
{
my $date = $1;
$date =~ /(\d{4})(\d{2})(\d{2})/;
my $folder = "$1-$2-$3";
# if the directory doesn't exist
if(!(-e -d "${dir}/${folder}"))
{
mkdir("${dir}/${folder}");
}
move("${dir}/$file","${dir}/${folder}");
}
}
}
Thanks for the contributions.

Execute Command In Multiple Directories

I have a large set of single install WordPress sites on my Linux server. I want to create text files that contain directory names for groups of my sites.
For instance all my sites live in /var/www/vhosts and I may want to group a set of 100 websites in a text file such as
site1
site2
site3
site4
How can I write a script that will loop through only the directories specified in the group text files and execute a command. My goal is to symlink some of the WordPress plugins and I don't want to have to manually go directory by directory if I can just create groups and execute the command within that group of directories.
For each site in the group file, go to the /wp-content/plugins folder and execute the symlink command specified.
Depending on your goals, you may be able to achieve that with a one-liner using find and an -exec action. I tend to like doing it as a Bash loop, because it is easier to add additional commands instead of having a long and unwieldy command doing it all, as well as handle errors.
I do not know if this is what you intend, but here is a proposal.
#!/bin/bash
# Receives site group file as argument1
# Receives symlink name as argument 2
# Further arguments will be passed to the command called
sites_dir="/var/www/vhosts"
commands_dir="wp-content/plugins"
if ! [[ -f $1 ]] ; then
echo "Site list not found : $1"
exit
fi
while IFS= read -r site
do
site_dir="$sites_dir/$site"
if ! [[ -d $site_dir ]] ; then
echo "Unknown site : $site"
continue
fi
command="$site_dir/$commands_dir/$2"
if ! [[ -x $command ]] ; then
echo "Missing or non-executable command : $command"
continue
fi
"$command" "${#:3}"
done <"$1"

Bash script function call error

I am writing my first Bash script and am running into a syntax issue with a function call.
Specifically, I want to invoke my script like so:
sh myscript.sh -d=<abc>
Where <abc> is the name of a specific directory inside of a fixed parent directory (~/app/dropzone). If the child <abc> directory doesn't exist, I want the script to create it before going to that directory. If the user doesn't invoke the script with a -d argument at all, I want the script to exist with a simple usage message. Here's my best attempt at the script so far:
#!/bin/bash
dropzone="~/app/dropzone"
# If the directory the script user specified exists, overwrite dropzone value with full path
# to directory. If the directory doesn't exist, first create it. If user failed to specify
# -d=<someDirName>, exit the script with a usage statement.
validate_args() {
args=$(getopt d: "$*")
set -- $args
dir=$2
if [ "$dir" ]
then
if [ ! -d "${dropzone}/targets/$dir" ]
then
mkdir ${dropzone}/targets/$dir
fi
dropzone=${dropzone}/targets/$dir
else
usage
fi
}
usage() {
echo "Usage: $0" >&2
exit 1
}
# Validate script arguments.
validate_args $1
# Go to the dropzone directory.
cd dropzone
echo "Arrived at dropzone $dropzone."
# The script will now do other stuff, now that we're in the "dropzone".
# ...etc.
When I try running this I get the following error:
myUser#myMachine:~/app/scripts$ sh myscript.sh -dyoyo
mkdir: cannot create directory `/home/myUser/app/dropzone/targets/yoyo': No such file or directory
myscript.sh: 33: cd: can't cd to dropzone
Arrived at dropzone /home/myUser/app/dropzone/targets/yoyo.
Where am I going wrong, and is my general approach even correct? Thanks in advance!
Move the function definitions to the top of the script (below the hash-bang). bash is objecting to the undefined (at that point) call to validate_args. usage definition should precede the definition of validate_args.
There also should be spacing in the if tests "[ " and " ]".
if [ -d "$dropzone/targets/$1" ]
The getopt test for option d should be-:
if [ "$(getopt d "$1")" ]
Here is a version of validate_args that works for me.
I also had to change the drop zone as on my shell ~ wouldn't expand in mkdir command.
dropzone="/home/suspectus/app/dropzone"
validate_args() {
args=$(getopt d: "$*")
set -- $args
dir=$2
if [ "$dir" ]
then
if [ ! -d "${dropzone}/targets/$dir" ]
then
mkdir ${dropzone}/targets/$dir
fi
dropzone=${dropzone}/targets/$dir
else
usage
fi
}
To pass in all args use $* as parameter -:
validate_args $*
And finally call the script like this for getopt to parse correctly-:
myscript.sh -d dir_name
When invoked, a function is indistinguishable from a command — so you don't use parentheses:
validate_args($1) # Wrong
validate_args $1 # Right
Additionally, as suspectus points out in his answer, functions must be defined before they are invoked. You can see this with the script:
usage
usage()
{
echo "Usage: $0" >&2
exit 1
}
which will report usage: command not found assuming you don't have a command or function called usage available. Place the invocation after the function definition and it will work fine.
Your chosen interface is not the standard Unix calling convention for commands. You'd normally use:
dropzone -d subdir
rather than
dropzone -d=subdir
However, we can handle your chosen interface (but not using getopts, the built-in command interpreter, and maybe not using GNU getopt either, and certainly not using getopt as you tried to do so). Here's workable code supporting -d=subdir:
#!/bin/bash
dropzone="$HOME/app/dropzone/targets"
validate_args()
{
case "$1" in
(-d=*) dropzone="$dropzone/${1#-d=}"; mkdir -p $dropzone;;
(*) usage;;
esac
}
usage()
{
echo "Usage: $0 -d=dropzone" >&2
exit 1
}
# Validate script arguments.
validate_args $1
# Go to the dropzone directory.
cd $dropzone || exit 1
echo "Arrived at dropzone $dropzone."
# The script will now do other stuff, now that we're in the "dropzone".
# ...etc.
Note the cautious approach with the cd $dropzone || exit 1; if the cd fails, you definitely do not want to continue in the wrong directory.
Using the getopts built-in command interpreter:
#!/bin/bash
dropzone="$HOME/app/dropzone/targets"
usage()
{
echo "Usage: $0 -d dropzone" >&2
exit 1
}
while getopts d: opt
do
case "$opt" in
(d) dropzone="$dropzone/$OPTARG"; mkdir -p $dropzone;;
(*) usage;;
esac
done
shift $(($OPTIND - 1))
# Go to the dropzone directory.
cd $dropzone || exit 1
echo "Arrived at dropzone $dropzone."
# The script will now do other stuff, now that we're in the "dropzone".
# ...etc.

Resources