#!/bin/bash
for dir in /home/username/git/*/
do
for file in "$dir"/*
do
if [[ -f $file ]]
then
echo "$file"
fi
done
done
When I try to run it. I got
syntax error near unexpected toke' `do
'rocTest.sh: line 3: `do
Why?
Use "$file" (with quotes) consistently to deal with "problematic" file names; in particular if [[ -f $file ]] should be
if [[ -f "$file" ]] ...
Note that bash is not always in /bin (e.g. FreeBSD places it in /usr/local/bin); for wider portability, either use
#!/usr/bin/env bash
or #!/bin/sh and make sure to remove bash-isms (e.g. use checkbashisms on Debian/Ubuntu). E.g. write if test -f "$file" instead of [[ -f "$file" ]]
Related
I want to move all JSON files created within a jenkins job to a different folder.
It is possible that the job does not create any json file.
In that case the mv command is raising an error and so that job is failing.
How do I prevent mv command from raising error in case no file is found?
Welcome to SO.
Why do you not want the error?
If you just don't want to see the error, then you could always just throw it away with 2>/dev/null, but PLEASE don't do that. Not every error is the one you expect, and this is a debugging nightmare. You could write it to a log with 2>$logpath and then build in logic to read that to make certain it's ok, and ignore or respond accordingly --
mv *.json /dir/ 2>$someLog
executeMyLogParsingFunction # verify expected err is the ONLY err
If it's because you have set -e or a trap in place, and you know it's ok for the mv to fail (which might not be because there is no file!), then you can use this trick -
mv *.json /dir/ || echo "(Error ok if no files found)"
or
mv *.json /dir/ ||: # : is a no-op synonym for "true" that returns 0
see https://www.gnu.org/software/bash/manual/html_node/Conditional-Constructs.html
(If it's failing simply because the mv is returning a nonzero as the last command, you could also add an explicit exit 0, but don't do that either - fix the actual problem rather than patching the symptom. Any of these other solutions should handle that, but I wanted to point out that unless there's a set -e or a trap that catches the error, it shouldn't cause the script to fail unless it's the very last command.)
Better would be to specifically handle the problem you expect without disabling error handling on other problems.
shopt -s nullglob # globs with no match do not eval to the glob as a string
for f in *.json; do mv "$f" /dir/; done # no match means no loop entry
c.f. https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html
or if you don't want to use shopt,
for f in *.json; do [[ -e "$f" ]] && mv "$f" /dir/; done
Note that I'm only testing existence, so that will include any match, including directories, symlinks, named pipes... you might want [[ -f "$f" ]] && mv "$f" /dir/ instead.
c.f. https://www.gnu.org/software/bash/manual/html_node/Bash-Conditional-Expressions.html
This is expected behavior -- it's why the shell leaves *.json unexpanded when there are no matches, to allow mv to show a useful error.
If you don't want that, though, you can always check the list of files yourself, before passing it to mv. As an approach that works with all POSIX-compliant shells, not just bash:
#!/bin/sh
# using a function here gives us our own private argument list.
# that's useful because minimal POSIX sh doesn't provide arrays.
move_if_any() {
dest=$1; shift # shift makes the old $2 be $1, the old $3 be $2, etc.
# so, we then check how many arguments were left after the shift;
# if it's only one, we need to also check whether it refers to a filesystem
# object that actually exists.
if [ "$#" -gt 1 ] || [ -e "$1" ] || [ -L "$1" ]; then
mv -- "$#" "$dest"
fi
}
# put destination_directory/ in $1 where it'll be shifted off
# $2 will be either nonexistent (if we were really running in bash with nullglob set)
# ...or the name of a legitimate file or symlink, or the string '*.json'
move_if_any destination_directory/ *.json
...or, as a more bash-specific approach:
#!/bin/bash
files=( *.json )
if (( ${#files[#]} > 1 )) || [[ -e ${files[0]} || -L ${files[0]} ]]; then
mv -- "${files[#]}" destination/
fi
Loop over all json files and move each of them, if it exists, in a oneliner:
for X in *.json; do [[ -e $X ]] && mv "$X" /dir/; done
Hi everyone I need to check if a file exist with a shell script. I did some digging and ended up with this syntax but I'm not sure why it isn't working
(please bear in mind that you are talking to beginner)
I've found that you can add -e for example to check if it exist but I didn't get where these shortcuts came form or their names
#! /bin/bash
if [ "$#" = "1" ]
then
if [ -e $($1) ] && [ -f $($1) ]
then echo 'the file exists'
fi
fi
In idiomatic Bash:
#!/usr/bin/env bash
if [[ -f "${1-}" ]]
then
echo 'the file exists'
fi
Correct shebang
[[ rather than [
-f implies -e
No need for semicolons or single-use variables.
Please keep in mind that this does not tell you whether the file is a text file. The only "definition" of a text file as opposed to any other file is whether it contains only printable characters, and even that falls short of dealing with UTF BOM characters and non-ASCII character sets. For that you may want to look at the non-authoritative output of file "${1-}", for example:
$ file ~/.bashrc
/home/username/.bashrc: ASCII text
More in the Bash Guide.
#!/bin/bash
if [ "$#" == 1 ]; then
if [[ -e "$1" && -f "$1" ]]; then
echo 'The file exists';
fi
fi
You should put every conditional && between [[ ]] symbols otherwise it will be interpreted as execute if success.
#! /bin/sh
FILE=$1 # get filename from commandline
if [ -f $FILE ]; then
echo "file $FILE exists"
fi
See the fine manual page of test commands, which are built-in in the different shells: man test; man sh; man bash
You will also find many shell primers which explain this very nicely.
Or see bash reference manual: https://www.gnu.org/software/bash/manual/bash.pdf
I am trying to see if the /dev/rfcomm0 file exists but in the way as described below by using the normal [ -f $file ] way I constantly get the message that the file does not exist. But I am able to read the file manually with cat.
looking for /etc/hosts is just for controling that I'm not getting insane.
file="/dev/rfcomm0"
file2="/etc/hosts"
while :
do
[ -f $file2 ] && echo "Found host" || echo "Not found host"
[ -f $file ] && echo "Found rfcomm" || echo "not found rfcomm"
the manual view of /dev/rfcomm
root#raspberrypi:~# cat /dev/rfcomm0
[null,"0,0,0","255,255,255",5,50,"Nikske"]
Probably I'm missing something very important but I've been looking for a long time after a solution.
The -f flag specifically states in the bash documentation (my emphasis):
True if file exists and is a regular file
So I'd warrant that the file you're looking at is not a regular file, especially since it's in the /dev directory where all sorts of non-regular files tend to exist.
Case in point (on my system with a character-special file):
pax$ ls -al /dev/rfkill
crw-rw-r--+ 1 root netdev 10, 62 Feb 13 17:49 /dev/rfkill
pax$ [[ -f /dev/rfkill ]] && echo regular || echo not regular
not regular
pax$ [[ -e /dev/rfkill ]] && echo exists || echo does not exist
exists
pax$ [[ -c /dev/rfkill ]] && echo char special || echo not char special
char special
I'd suggest checking what sort of file it is with ls -al and using the appropriate [[ flag for that (see, for example, CONDITIONAL EXPRESSIONS in the output of man bash).
I'm getting an error message "./query.sh: line 5: ${1,,}: bad substitution" whenever I run a shell script in a Mac OSX terminal
#!/bin/bash
dir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
if [ "$1" != "" ]; then
letter1=$(echo ${1,,}|(cut -b1))new
if [[ $letter1 == [a-zA-Z0-9] ]]; then
if [ -f "$dir/data/$letter1" ]; then
grep -ai "^$1" "$dir/data/$letter1"
else
letter2=$(echo ${1,,}|cut -b2)
if [[ $letter2 == [a-zA-Z0-9] ]]; then
if [ -f "$dir/data/$letter1/$letter2" ]; then
grep -ai "^$1" "$dir/data/$letter1/$letter2"
else
letter3=$(echo ${1,,}|cut -b3)
if [[ $letter3 == [a-zA-Z0-9] ]]; then
if [ -f "$dir/data/$letter1/$letter2/$letter3" ]; then
grep -ai "^$1" "$dir/data/$letter1/$letter2/$letter3"
fi
else
if [ -f "$dir/data/$letter1/$letter2/symbols" ]; then
grep -ai "^$1" "$dir/data/$letter1/$letter2/symbols"
fi
fi
fi
else
if [ -f "$dir/data/$letter1/symbols" ]; then
grep -ai "^$1" "$dir/data/$letter1/symbols"
fi
fi
fi
else
if [ -f "$dir/data/symbols" ]; then
grep -ai "^$1" "$dir/data/symbols"
fi
fi
else
echo "[*] Example: ./query name#domain.com"
fi
The scripts function is to search throuhg a huge number of data files so could anybody help me pinpoint the source of the problem ?
The ,, operator was introduced in bash 4.0, but /bin/bash on macOS is still version 3.2. You can install a newer version of bash and change your shebang accordingly, or you can use letter1=$(echo "$1" | tr '[:upper:]' '[:lower:]' | cut -b1) instead.
(You can, however, use ${letter:0:1}, ${letter:1:1}, etc, in place of a call to cut to get a single letter from the string.)
My advice is to treat /bin/bash on macOS as nothing more than a POSIX-compatible shell. Use #!/bin/sh if you want a portable script, or use #!/usr/local/bin/bash (or whatever is appropriate, after installing a new version of bash) if you want to take advantage of bash extensions at the expense of portability.
If you are using a subshell to produce the value anyway, you might as well change the call to something which is portable back to earlier versions of Bash.
Running cut -b1 in a subshell (in parentheses) is useless and doing it after the conversion means you convert a potentially long string and then discard everything except the first character.
letter1=$(echo "${1:0:1}" | tr '[:upper:]' '[:lower:]')new
Notice also the double quotes around the argument to echo.
Do you really want the suffix new to be attached to the value? The first condition in the if can never match in that case.
You should probably also refactor the code to avoid the deep "arrow antipattern" and only perform the grep once you have a value for the file name you want to search.
As mentioned in other answers, the builtin bash on Mac is very old and must be upgraded to use some operators, e.g. the caret operator. If you use homebrew, run brew install bash and follow the instructions in this post.
I am using ubuntu, fluxbox, pcmanfm as filemanager, xmms2 as music player.
My goal: add songs to xmms2 playlist easily with pcmanfm.
I have this script that works for single files:
path= $1
if [ -d "$path" ]; then #if directory
xmms2 radd "$path"
else
if [ -e "$path" ]; then #if not directory, but file
xmms2 add "$path"
fi
fi
I also want to have ability to add group of files
I mean select all of them and then rigth-click -> open with -> xmms2_add_script
I thougth that same code in loop should work (if pcmanfm passes just more then one argument):
args=("$#")
for path in $args; do
if [ -d "$path" ]; then
xmms2 radd "$path"
else
if [ -e "$path" ]; then
xmms2 add "$path"
fi
fi
done
but it does not work.
(I know that there is some problem running for loop through filenames with whitespaces, so tried only non-whitespaced files.)
I tried loging the output adding this
echo date >> /home/me/output.txt
echo xmms2 radd "$path" >> /home/me/output.txt
in if statements.
It seems that program is called only once when I try adding group of files.
Maybe someone knows how pcmanfm opens multiple files with some program?
(I guess other programs does it same way)
Or maybe someone just know how can I achieve my goal with a bash script?
Just in case: to add item to xmms2 playlist "xmms2 radd directory_name" or "xmms2 add file_name"
I have only basic understanding of bash so would be nice if answers wouldn't for expirienced bash programmers :)
Change your for loop to this:
for path in "${args[#]}"; do
The for loop loops over its arguments, your args variable is an array, but bash only sees the first element when you do $args.
Greg's Bash FAQ/Wiki: How can I use array variables?
This is actually how I would write that to be more readable:
for x in "$#"; do
[[ -d $x ]] && xmms2 radd "$x"
[[ -f $x ]] && xmms2 add "$x"
done