don't execute for file in ls do - linux

I have a script that has to process some files (name beginning with AB) in a directory.
The code is :
for file in AB*
do
cp ...
...
done
When there are no *.txt files in the folder the code executes anyway 1 time.
But then there are errors because I try to copy a file that doesn't exist.
How can I make that the do-command doesn't execute when the result of the ls-command is empty?
I already tried using ls, quotes an combinations > nothing gives the result I want.

Maybe you can add a condition before:
if [ $(ls AB* 2>/dev/null) ]; then
for ...
fi
with 2>/dev/null you catch the errors not to be printed.

The other answers are just plain wrong in Bash. Do not use them! Please, always observe this rule:
Each time you use globs in Bash, use them with either shopt -s nullglob or shopt -s failglob.
If you observe this rule, you'll always be safe. In fact, each time you don't observe this rule, God kills a kitten.
shopt -s nullglob: in this case, a non-matching glob expands to nothing. Look:
$ mkdir Test; cd Test
$ shopt -u nullglob # I'm explicitly unsetting nullglob
$ echo *
*
$ for i in *; do echo "$i"; done
*
$ # Dear, God has killed a kitten :(
$ # but it was only for demonstration purposes, I swear!
$ shopt -s nullglob # Now we're going to save lots of kittens
$ echo *
$ for i in *; do echo "$i"; done
$ # Wow! :)
shopt -s failglob: in this case, Bash will raise an explicit error when a glob has no expansions. Look:
$ mkdir Test; cd Test
$ shopt -u nullglob # Unsetting nullglob
$ shopt -s failglob # Setting failglob for the love of kittens
$ echo *
bash: no match: *
$ # cool :) what's the return code of this?
$ echo $?
1
$ # who cares, anyway? and a for loop?
$ for i in *; do echo "$i"; done
bash: no match: *
$ # cool :)
Using either nullglob or failglob, you're sure to not launch random commands with uncontrolled arguments!
Cheers!

You probably need the bash test builtin, often abbreviated as [ , sothing like
if [ -f output.txt ] ; then
beware: spaces are important above.

Related

How do I write a one-liner cd command for the following case?

I have 2 directories
testing_dir
testing_dir_win
So I need to cd to testing_dir. But here is the case
the directories can be
testing_dir or testing_dir-2.1.0
testing_dir_win or testing_dir_win-1.3.0
and my script should only take testing_dir or testing_dir-2.1.0 (based on which is available)
I have the long way of writing it:
str=`ls folder_name|grep ^testing_dir`
arr=(${str//" "/ })
ret=""
for i in "${arr[#]}"
do
if [[ $i != *"testing_dir_win"* ]] ; then
ret=$i
fi
done
but is there a one-liner for this problem? something like cd testing_dir[\-]?(This doesn't work by the way).
If your script contains
shopt -s extglob
you can use:
cd testing_dir?(-[[:digit:]]*) || exit
...if you have a guarantee that only one match will exist.
Without that guarantee, you can directly set
arr=( testing_dir?(-[[:digit:]]*) )
cd "${arr[0]}" || exit
use command with grep filters:
cd `ls | grep -w testing_dir`
this command will match the testing_dir directory without worrying for version.
P.S in case of many versions it will go inside the earliest version so add "head -1, tail -1" according to your usecase

Parameter Substitution replace with Alternation (OR) |

I'm trying to replace a extension of a filename considering case but without success.
#!/bin/bash
pdf_file="/root/users/test.pdf"
jpg_file="${pdf_file/.pdf/.jpg}"
echo $jpg_file
I tried it, but it doesn't work:
jpg_file="${pdf_file/(.pdf|.PDF)/.jpg}"
You could use a glob pattern like this:
$ echo "${pdf_file/.[Pp][Dd][Ff]/.jpg}"
/root/users/test.jpg
If you use extended glob patterns (shopt -s extglob), you could use this instead:
$ echo "${pdf_file/.#(PDF|pdf)/.jpg}"
/root/users/test.jpg
Or you could use the shell option to ignore case when matching:
$ shopt -s nocasematch
$ pdf_file="/root/users/test.PDF"
$ echo "${pdf_file/.pdf/.jpg}"
/root/users/test.jpg
Remark valid for all three solutions: ${parameter/pattern/string} replaces the pattern wherever it occurs, but the extension is likely at the end – we can make sure we'll only replace it at the end:
echo "${pdf_file%.[Pp][Dd][Ff]}.jpg"
which works in any POSIX shell, or
shopt -s extglob
echo "${pdf_file%.#(PDF|pdf)}.jpg"
or
shopt -s nocasematch
pdf_file="/root/users/test.PDF"
echo "${pdf_file%.pdf}.jpg"

crontab not executing complex bash script

SOLVED! add #!/bin/bash at the top of all my scripts in order to make use of bash extensions. Otherwise it restricts itself to POSIX shell syntax. Thanks Barmar!
Also, I'll add that I had trouble with gpg decryption not working from cronjob after I got it executing, and the answer was to add the --no-tty option (no terminal output) to the gpg command.
I am fairly new to linux, so bear with me...
I am able to execute a simple script with crontab -e when logged in as ubuntu:
* * * * * /ngage/extract/bin/echoer.sh
and this bash script simply prints output to a file:
echo "Hello" >> output.txt
But when I try to execute my more complex bash script in exactly the same way, it doesn't work:
* * * * * /ngage/extract/bin/superMasterExtract.sh
This script called into other bash scripts. There are 4 scripts in total, which 3 levels of hierarchy. It goes superMasterExtract > masterExtract > (decrypt, unzip)
Here is the code for superMasterExtract.sh (top level):
shopt -s nullglob # ignore empty file
cd /str/ftp
DIRECTORY='writeable'
for d in */ ; do # for all directories in /str/ftp
if [ -d "$d$DIRECTORY" ]; then # if the directory contains a folder called 'writeable'
files=($d$DIRECTORY/*)
dirs=($d$DIRECTORY/*/)
numdirs=${#dirs[#]}
numFiles=${#files[#]}
((numFiles-=$numdirs))
if [ $numFiles -gt 0 ]; then # if the folder has at least one file in it
bash /ngage/extract/bin/masterExtract.sh /str/ftp ${d:0:${#d} - 1} # execute this masterExtract bash script with two parameters passed in
fi
fi
done
masterExtract.sh:
DATE="$(date +"%m-%d-%Y_%T")"
LOG_FILENAME="log$DATE"
LOG_FILEPATH="/ngage/extract/logs/$2/$LOG_FILENAME"
echo "Log file is $LOG_FILEPATH"
bash /ngage/extract/bin/decrypt.sh $1 $2 $DATE
java -jar /ngage/extract/bin/sftp.jar $1 $2
bash /ngage/extract/bin/unzip.sh $1 $2 $DATE
java -jar /ngage/extract/bin/sftp.jar $1 $2
echo "Log file is $LOG_FILEPATH"
decrypt.sh:
shopt -s nullglob
UPLOAD_FILEPATH="$1/$2/writeable"
DECRYPT_FOLDER="$1/decryptedFiles/$2"
HISTORY_FOLDER="$1/encryptHistory/$2"
DONE_FOLDER="$1/doneFiles/$2"
LOG_FILENAME="log$3"
LOG_FILEPATH="/ngage/extract/logs/$2/$LOG_FILENAME"
echo "DECRYPT_FOLDER=$DECRYPT_FOLDER" >> $LOG_FILEPATH
echo "HISTORY_FOLDER=$HISTORY_FOLDER" >> $LOG_FILEPATH
cd $UPLOAD_FILEPATH
for FILE in *.gpg;
do
FILENAME=${FILE%.gpg}
echo ".done FILE NAME=$UPLOAD_FILEPATH/$FILENAME.done" >> $LOG_FILEPATH
if [[ -f $FILENAME.done ]]; then
echo "DECRYPTING FILE=$UPLOAD_FILEPATH/$FILE INTO $DECRYPT_FOLDER/$FILENAME" >> $LOG_FILEPATH
cat /ngage/extract/.sftpPasswd | gpg --passphrase-fd 0 --output "$DECRYPT_FOLDER/$FILENAME" --decrypt "$FILE"
mv $FILE $HISTORY_FOLDER/$FILE
echo "MOVING FILE=$UPLOAD_FILEPATH/$FILE INTO $HISTORY_FOLDER/$FILE" >> $LOG_FILEPATH
else
echo "Done file not found!" >> $LOG_FILEPATH
fi
done
cd $DECRYPT_FOLDER
for FILE in *
do
mv $FILE $DONE_FOLDER/$FILE
echo "DECRYPTED FILE=$DONE_FOLDER/$FILE" >> $LOG_FILEPATH
done
If anyone has a clue why it refuses to execute my more complicated script, I'd love to hear it. I have also tried setting some environment variables at the beginning of crontab as well:
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/local/bin:/usr/bin
MAILTO=jgardnerx85#gmail.com
HOME=/
* * * * * /ngage/extract/bin/superMasterExtract.sh
Note, I don't know that these are the appropriate variables for my installation or my script. I just pulled them off other posts and tried it to no avail. If these aren't the correct environment variables, can someone tell me how I can deduce the right ones for my particular application?
You need to begin your script with
#!/bin/bash
in order to make use of bash extensions. Otherwise it restricts itself to POSIX shell syntax.

Create symbolic links transforming file names - Bash Script

I have a bash script that will loop through a directory getting each file name. What I would like to do is create a few symbolic links for these files. Except I want to change the link names.
Example 1:
File Name: testFile.so.3.4.5
ln -s testFile.so.3.4.5 testFile.so.3
ln -s testFile.so.3 testFile.so
Example 2:
File Name: testLink.so.4.4
ln -s testLink.so.4.4 testLink.so.4
ln -s testLink.so.4 testLink.so
So I need to transform the file name twice. The first time removing everything except the first number after *.so. The second time removing everything after *.so.
This is what I have so far. I know it's not much:
#! /bin/bash
# clear any info on screen
clear
# greeting
echo "Starting the script!"
# loop through all files in the directory
for f in *
do
echo "Processing: $f"
done
I'm a bit new to bash and file name transformations, so any help or guidance would be appreciated.
Using a combination of bash extended regular expressions and parameter expansion
for file in *.so.*
do
regex='(.*\.so\.[^.]*)\..*'
if [[ $file =~ $regex ]]
then
tempfile="${BASH_REMATCH[1]}"
ln -s "$file" "$tempfile"
ln -s "$tempfile" "${tempfile%.*}"
fi
done
Also more generally, using only parameter expansion:
for f in *.so.*.*
do
if [ -e "$f" ]; then
base=${f%".${f#*.so.*.*}"}
ln -s "$f" "$base"
ln -s "$base" "${base%.*}"
fi
done
or more generally:
files='libfoo.so.1.2.3.4.5 libbar.so libqux.so.1'
for f in $files; do
while test ${f##*.} != so; do
link=${f%.*}
ln -s $f $link
f=$link
done
done
this will create libfoo.so.1.2.3.4 -> libfoo.so.1.2.3.4.5, libfoo.so.1.2.3 -> libfoo.so.1.2.3.4, libfoo.so.1.2 -> libfoo.so.1.2.3, libfoo.so.1 -> libfoo.so.1.2, libfoo.so -> libfoo.so.1, and libqux.so -> libqux.so.1; libbar.so will be ignored.

Validate script's argument by file extension?

I am writing a script which you can pass a file name into as an argument and it'll only run if it's a certain file extension.
flac2mp3 "01 Song.flac"
or
flac2mp3 "01 Song.FLAC"
I know there a lot of scripts out there showing you how to convert flac to mp3, but this is my script and I want to learn how to write the script using this method.
It's so I can learn arguments and for when I feel like converting only 1 individual file. (for multiple files I just wrote a for loop with *.flac inside the script)
I just want to learn how to check if the $1 argument contains *.[Ff][Ll][Aa][Cc]
Here's what I cobbled up together from the internet so far (which I know is embarrassingly wrong but I wanted to show what I was going for) :
#!/bin/bash
#flac2mp3
if [ -z $1 ] && [[$1 !=~ *.[Ff][Ll][Aa][Cc]]];then echo "Give FLAC File Name"; exit 0;fi
OUTF=${1%.flac}.mp3
ARTIST=$(metaflac "$1" --show-tag=ARTIST | sed s/.*=//g)
TITLE=$(metaflac "$1" --show-tag=TITLE | sed s/.*=//g)
ALBUM=$(metaflac "$1" --show-tag=ALBUM | sed s/.*=//g)
GENRE=$(metaflac "$1" --show-tag=GENRE | sed s/.*=//g)
TRACKNUMBER=$(metaflac "$1" --show-tag=TRACKNUMBER | sed s/.*=//g)
DATE=$(metaflac "$1" --show-tag=DATE | sed s/.*=//g)
flac -c -d "$1" | lame -m j -q 0 --vbr-new -V 0 -s 44.1 - "$OUTF"
id3 -t "$TITLE" -T "${TRACKNUMBER:-0}" -a "$ARTIST" -A "$ALBUM" -y "$DATE" -g "${GENRE:-12}" "$OUTF"
done
Please and Thank Your for the help.
Try the following code:
shopt -s nocasematch
if [[ $1 == *flac ]]; then
echo "ok"
fi
This is case insensitive.
EDIT
$ LANG=C help shopt
shopt: shopt [-pqsu] [-o] [optname ...]
Set and unset shell options.
Change the setting of each shell option OPTNAME. Without any option
arguments, list all shell options with an indication of whether or not each
is set.
Options:
-o restrict OPTNAMEs to those defined for use with `set -o'
-p print each shell option with an indication of its status
-q suppress output
-s enable (set) each OPTNAME
-u disable (unset) each OPTNAME
Exit Status:
Returns success if OPTNAME is enabled; fails if an invalid option is
given or OPTNAME is disabled.
If you run shopt alone in a shell, you will see al options available :
$ shopt
autocd on
cdable_vars on
cdspell off
checkhash off
checkjobs off
checkwinsize off
cmdhist on
compat31 off
compat32 off
compat40 off
compat41 off
direxpand off
dirspell off
dotglob on
execfail off
expand_aliases on
extdebug off
extglob on
extquote on
failglob off
force_fignore on
globstar on
gnu_errfmt off
histappend on
histreedit off
histverify off
hostcomplete off
huponexit off
interactive_comments on
lastpipe off
lithist off
login_shell off
mailwarn off
no_empty_cmd_completion off
nocaseglob off
nocasematch off
nullglob off
progcomp on
promptvars on
restricted_shell off
shift_verbose off
sourcepath on
xpg_echo off
To know what does all these options :
man bash | less +/'^SHELL BUILTIN COMMANDS'
then search `shopt from within this section.

Resources