I created a script that executes some commands based on a condition. If directory contains files then run "screen -r" anything else run "screen". Problem is screen sometimes gets executed even when the directory contains files.
if [ "$(ls -A $DIR)" ]; then
screen -r
else
screen
fi
What I want to do is refine it and break it down into two statements. If directory contains files then run screen-r" & if a directory doesn't contain files run "screen"
if [ "$(ls -A $DIR)" ]; then
screen -r
fi
&
if ["$(directory without files)"] ; then
screen
fi
Maybe even a statement that executes based on # of file. If directory contains X amount of files.
Can somebody help me out with this? I hope I explained what I want thoroughly.
Thanks,
Geofferey
Again thank you for all your help, I got it all working now. Here is the final script. It's for the iPhone and an app I'm making called MobileTerm Backgrounder.
#Sets up terminal environment?
if [[ $TERM = network || -z $TERM ]]; then
export TERM=linux
fi
# This script automatically runs screen & screen -r (resume) based on a set of conditions.
# Variable DIR (variable could be anything)
DIR="/tmp/screens/S-mobile"
# if /tmp/screens/S-mobile list files then run screen -x
if [ "$(ls -A $DIR)" ]; then
screen -x
fi
#if /tmp/screens/S-mobile contains X amount of files = to 0 then run screen -q
if [ $(ls -A "$DIR" | wc -l) -eq 0 ]; then
screen -q
fi
find could be helpful here:
if [[ $(find ${dir} -type f | wc -l) -gt 0 ]]; then echo "ok"; fi
UPD: what is -gt?
man bash -> / -gt/:
arg1 OP arg2
OP is one of -eq, -ne, -lt, -le, -gt, or -ge. These arithmetic binary operators return true if arg1 is equal to, not
equal to, less than, less than or equal to, greater than, or greater than or equal to arg2, respectively. Arg1 and arg2
may be positive or negative integers.
So, -gt is "greater than" boolean function.
I would use ls and wc this way:
if [ $(ls -A "$DIR" | wc -l) -gt 0 ]; then
screen -r
else
screen
fi
You have to double quote the $DIR variable otherwise you'll have problems with directory names that contains spaces.
This seems simpler to me
if [ `ls -1q /some/dir | wc -l` -eq 0 ]; then echo "huzzah"; fi
The ls outputs one line per file and wc counts the lines
Related
I am new to bash scripting and I have to create this script that takes 3 directories as arguments and copies in the third one all the files in the first one that are NOT in the second one.
I did it like this:
#!/bin/bash
if [ -d $1 && -d $2 && -d $3 ]; then
for FILE in [ ls $1 ]; do
if ! [ find $2 -name $FILE ]; then
cp $FILE $3
done
else echo "Error: one or more directories are not present"
fi
The error I get when I try to execute it is: "line 7: syntax error near unexpected token `done' "
I don't really know how to make it work!
Also even if I'm using #!/bin/bash I still have to explicitly call bash when trying to execute, otherwise it says that executing is not permitted, anybody knows why?
Thanks in advance :)
Couple of suggestions :
No harm double quoting variables
cp "$FILE" "$3" # prevents wordsplitting, helps you filenames with spaces
for statement fails for the fundamental reason -bad syntax- it should've been:
for FILE in ls "$1";
But then, never parse ls output. Check [ this ].
for FILE in ls "$1"; #drastic
Instead of the for-loop in step2 use a find-while-read combination:
find "$1" -type f -print0 | while read -rd'' filename #-type f for files
do
#something with $filename
done
Use lowercase variable names for your script as uppercase variables are reserved for the system. Check [this].
Use tools like [ shellcheck ] to improve script quality.
Edit
Since you have mentioned the input directories contain only files, my alternative approach would be
[[ -d "$1" && -d "$2" && -d "$3" ]] && for filename in "$1"/*
do
[ ! -e "$2/${filename##*/}" ] && cp "$filename" "$3"
done
If you are baffled by ${filename##*/} check [ shell parameter expansion ].
Sidenote: In linux, although discouraged it not uncommon to have non-standard filenames like file name.
Courtesy: #chepner & #mklement0 for their comments that greatly improved this answer :)
Your script:
if ...; then
for ...; do
if ...; then
...
done
else
...
fi
Fixed structure:
if ...; then
for ...; do
if ...; then
...
fi # <-- missing
done
else
...
fi
If you want the script executable, then make it so:
$ chmod +x script.sh
Notice that you also have other problems in you script. It is better written as
dir1="$1"
dir2="$2"
dir3="$3"
for f in "$dir1"/*; do
if [ ! -f "$dir2/$(basename "$f")" ]; then
cp "$f" "$dir3"
fi
done
this is not totally correct:
for FILE in $(ls $1); do
< whatever you do here >
done
There is a big problem with that loop if in that folder there is a filename like this: 'I am a filename with spaces.txt'.
Instead of that loop try this:
for FILE in "$1"/*; do
echo "$FILE"
done
Also you have to close every if statement with fi.
Another thing, if you are using BASH ( #!/usr/bin/env bash ), it is highly recommended to use double brackets in your test conditions:
if [[ test ]]; then
...
fi
For example:
$ a='foo bar'
$ if [[ $a == 'foo bar' ]]; then
> echo "it's ok"
> fi
it's ok
However, this:
$ if [ $a == 'foo bar' ]; then
> echo "it's ok";
> fi
bash: [: too many arguments
You've forgot fi after the innermost if.
Additionally, neither square brackets nor find do work this way. This one does what your script (as it is now) is intended to on my PC:
#!/bin/bash
if [[ -d "$1" && -d "$2" && -d "$3" ]] ; then
ls -1 "$1" | while read FILE ; do
ls "$2/$FILE" >/dev/null 2>&1 || cp "$1/$FILE" "$3"
done
else echo "Error: one or more directories are not present"
fi
Note that after a single run, when $2 and $3 refer to different directories, those files are still not present in $2, so next time you run the script they will be copied once more despite they already are present in $3.
actual_size="du -h $some_folder";
y='eval $actual_size`
max_size=2MB
if [[ y -lt $max_size ]]; then
echo `du -h $some_folder`
cp "$file" $some_folder` #$file points to some file
Whats wrong with above lines of code? I also tried
if [[ $(stat -c %s $some_folder) -lt $max_size ]]; then
any help would be appreciated, Thnaks!
In order to use POSIX du in a script it helps to have a wrapper function which only prints the output of its second to last field. This can be accomplished with awk, or here is how I do it using eval to expand the second to last argument to a function:
#!/bin/sh
duprint()
{
if [ "$#" -gt 1 ]
then printf "%s" $( eval printf '$'$(( $# - 1 )))
fi
}
duwrap()
{
duprint $(du -s $# 2>/dev/null)
}
duwrap "$#"
This is useful for instance if you want the total size of several directories in anticipation of copying them over to a new filesystem:
./duwrapper /bin /etc /lib /boot
Finally, one can use their du wrapper function to compare directory sizes:
if [ "$(duwrap /lib)" -gt "$(duwrap /bin)" ]
then echo "/lib is greater"
fi
I am writing a ksh function (that is placed in the .profile file) that will present a menu of subdirectories and permit the user to choose one into which to cd. Here is the code:
# Menu driven subdirectory descent.
d(){
# Only one command line argument accepted
[ "$1" = "--" ] && shift $# # Trap for "ls --" feature
wd=`pwd`; arg="${1:-$wd}"
dirs="`/bin/ls -AF $arg 2>/dev/null | grep /$ | tr -d \"/\"`"
# Set the names of the subdirectories to positional parameters
if [ "$dirs" ] ;then
set $dirs
if [ $# -eq 1 -a "$arg" = "$wd" ] ;then cd $arg/$1; return; fi # trap: it's obvious; do it
else echo "No subdirectories found" >&2; return 1
fi
# Format and display the menu
if [ `basename "${arg}X"` = "${arg}X" ] ;then arg="$wd/$arg"; fi # Force absolute path if relitive
echo -e "\n\t\tSubdirectories relative to ${arg}: \n"
j=1; for i; do echo -e "$j\t$i"; j=`expr $j + 1`; done | pr -r -t -4 -e3
echo -e "\n\t\tEnter the number of your choice -- \c "
# Convert user-input to directory-name and cd to it
read choice; echo
dir=`eval "(echo $\{"$choice"\})"` # Magic here.
[ "$choice" -a "$choice" -ge 1 -a "$choice" -le "$#" ] && cd $arg/`eval echo "$dir"`
}
This function works reasonably well with the exception of directory names that contain space characters. If the directory name contains a space, the set command sets each space delimited element of the directory name (instead of the complete directory name) into a separate positional parameter; that is not useful here.
I have attempted to set the $IFS shell variable (which contains a space, tab, and newline by default) to a single newline character with:
IFS=`echo` # echo outputs a trailing newline character by default
Which appears to accomplish what is intended as verified with:
echo -e "$IFS\c" | hexdump -c
But despite my best efforts (over the course of several days work) I have failed to set the entire directory names that contain spaces as values for positional parameters.
What am I missing?
Suggestions are hereby solicited and most welcome.
ADVAthanksNCE
Bob
Short answer: You can't do that. Don't try. See the ParsingLs page for an understanding of why programmatic use of ls is inherently error-prone.
You can't get -F behavior without implementing it yourself in shell (which is indeed feasible), but the following is the correct way to put a list of subdirectories into the argument list:
set -- */
If you don't want to have a literal / on the end of each entry:
set -- */ # put list of subdirectories into "$#"
set -- "${#%/}" # strip trailing / off each
Even better, though: Use an array to avoid needing eval magic later.
dirs=( */ )
dirs=( "${dirs[#]%/}" )
printf '%s\n' "${dirs[$choice]}" # emit entry at position $choice
Let's tie this all together:
d() {
destdir=$(
FIGNORE= # ksh93 equivalent to bash shopt -s dotglob
while :; do
subdirs=( ~(N)*/ ) # ksh93 equivalent to subdirs=( */ ) with shopt -s nullglob
(( ${#subdirs[#]} > 2 )) || break # . and .. are two entries
for idx in "${!subdirs[#]}"; do
printf '%d) %q\n' "$idx" "${subdirs[$idx]%/}" >&2
done
printf '\nSelect a subdirectory: ' >&2
read -r choice
if [[ $choice ]]; then
cd -- "${subdirs[$choice]}" || break
else
break
fi
done
printf '%s\n' "$PWD"
)
[[ $destdir ]] && cd -- "$destdir"
}
Although still not working, this version does pass shellcheck albeit with one exception:
3 # Menu driven subdirectory descent.
4 function d{
5 # Only one command line argument accepted
6 [ "$1" = "--" ] && shift $# # Trap for "ls --" feature
7 wd="$PWD"; arg="${1:-$wd}"
8 set -- "${#%/}" # Set the names of the subdirectories to positional parameters
9 if [ $# -eq 1 -a "$arg" = "$wd" ] ;then cd "$arg/$1" || exit 1; return; # trap: it's obvious; do it
10 else echo "No subdirectories found" >&2; return 1
11 fi
12 # Format and display the menu
13 if [[ $(basename "${arg}X") = "${arg}X" ]] ;then arg="$wd/${arg}"; fi # Force absolute path if relitive
14 echo -e "\n\t\tSubdirectories relative to ${arg}: \n"
15 j=1; for i; do echo -e "$j\t$i"; j=(expr $j + 1); done | pr -r -t -4 -e3
16 echo -e "\n\t\tEnter the number of your choice -- \c "
17 # Convert user-input to directory-name and cd to it
18 read -r choice; echo
19 dir=(eval "(echo $\{\"$choice\"\})") # Magic here.
20 [ "$choice" -a "$choice" -ge 1 -a "$choice" -le "$#" ] && cd "${arg}"/"$(eval echo "${dir}")" || exit 1
^SC2128 Expanding an array without an index only gives the first element.
21 }
Once I have incorporated your suggestions into the code, and made it functional, I'll post it here, and mark my question answered. Thank you for your kind assistance.
I've used the code you kindly wrote as a basis for the d function below. It pretty much does what I'd like, with a few little issues:
All subdirectory names that contain a SPACE character are surrounded by characters, but those that do not are not.
All subdirectory names that contain a SINGLE QUOTE character have that character escaped with a BACKSLASH character.
Given that 1 and 2 above cause no issues, they are acceptable, but not ideal.
After user input does the cd, the menu of subdirectory names is again looped through. This could be considered a feature, I suppose. I tried substituting a return for the brake commands in the sections of code following the cd commands, but was unsuccessful in overcoming the subsequent looped menu.
The inclusion of "." and ".." at the head of the menu of subdirectories is not ideal, and actually serves no good purpose.
------------------- Code Begins ------------------------------
d() {
if [ "$BASH" ] && [ "$BASH" != "/bin/sh" ]; then
echo "$FUNCNAME: ksh only";return 1
fi
FIGNORE= # ksh93 equivalent to bash shopt -s dotglob
if [ ${#} -gt 0 ] ;then # Only one command line argument accepted
cd -- "$1" && return 0
fi
if [ `ls -AF1|grep /|wc -l` -eq 1 ] ;then # cd if only one subdirectory
cd -- `ls -AF1|grep /` && return 0
fi
destdir=$(
while :; do
subdirs=( ~(N)*/ ) # ksh93 equivalent to subdirs=( */ ) with shopt -s nullglob
(( ${#subdirs[#]} > 2 )) || break # . and .. are two entries
echo -e "\n\t\tSubdirectories below ${PWD}: \n" >&2
for idx in "${!subdirs[#]}"; do
printf '%d) %q\n' "$idx" "${subdirs[$idx]%/}" >&2
done
printf '\nSelect a subdirectory: ' >&2
read -r
if [[ $REPLY ]]; then
cd -- "${subdirs[$REPLY]}" || break # Continue to loop through subdirectories after cding
else
break
fi
done
printf '%s\n' "$PWD"
)
--------------------------- Code Ends ------------------------------------
So, overall I'm very pleased, and consider myself very fortunate to have received the knowledgeable assistance of such an accomplished Unix wizard. I can't thank you enough.
I have a bash script to copy files from one location to another if the score within the file is less than 36.
I run this script once a month, and it worked before but now I'm getting the error:
line 5: [: -lt: unary operator expected
Here is the script:
#!/bin/bash
for f in `ls $1/*.html`
do
score=`grep -o -P '(?<=ADJ. SCORE: )-?[0-9]?[0-9]' $f`
if [ $score -lt 36 ]
then cp $f $2
fi
done
I'm not sure if the OS matters; I'm using OS X 10.7 and I've troubles in the past with my bash scripts that otherwise work great on Linux boxes.
Thanks in advance!
sehe is right,
Or you can do:
if [[ $score < 36 ]]
then
cp "$f" "$2"
fi
This happens when there was no match, $score is then the empty string.
A simple fix:
#!/bin/bash
for f in `ls $1/*.html`
do
score=`grep -o -P '(?<=ADJ. SCORE: )-?[0-9]?[0-9]' $f`
if [ -z $score ]
then
echo "No match in '$f'"
else
if [ "$score" -lt 36 ]
then
cp "$f" "$2"
fi
fi
done
I think you also need to be more aware of quoting requirements in shell scripting.
On my mac running Mountain Lion version 10.8.4 I don't see -P option with grep. So you can use perl for instead (re-using most of your script):
#!/bin/bash
for f in "${1}"/*.html; do # Don't parse ls
score=$(perl -ne "print $& if /(?<=ADJ. SCORE: )-?[0-9]?[0-9]/" "$f")
if [ "$score" -lt 36 ]; then
cp "$f" $2
fi
done
How can I use the test command for an arbitrary number of files, passed in using an argument with a wildcard?
For example:
test -f /var/log/apache2/access.log.* && echo "exists one or more files"
Currently, it prints
error: bash: test: too many arguments
This solution seems to me more intuitive:
if [ `ls -1 /var/log/apache2/access.log.* 2>/dev/null | wc -l ` -gt 0 ];
then
echo "ok"
else
echo "ko"
fi
To avoid "too many arguments error", you need xargs. Unfortunately, test -f doesn't support multiple files. The following one-liner should work:
for i in /var/log/apache2/access.log.*; do test -f "$i" && echo "exists one or more files" && break; done
By the way, /var/log/apache2/access.log.* is called shell-globbing, not regexp. Please see Confusion with shell-globbing wildcards and Regex for more information.
First, store files in the directory as an array:
logfiles=(/var/log/apache2/access.log.*)
Then perform a test on the count of the array:
if [[ ${#logfiles[#]} -gt 0 ]]; then
echo 'At least one file found'
fi
This one is suitable for use with the Unofficial Bash Strict Mode, no has non-zero exit status when no files are found.
The array logfiles=(/var/log/apache2/access.log.*) will always contain at least the unexpanded glob, so one can simply test for existence of the first element:
logfiles=(/var/log/apache2/access.log.*)
if [[ -f ${logfiles[0]} ]]
then
echo 'At least one file found'
else
echo 'No file found'
fi
If you wanted a list of files to process as a batch, as opposed to doing a separate action for each file, you could use find, store the results in a variable, and then check if the variable was not empty. For example, I use the following to compile all the .java files in a source directory.
SRC=`find src -name "*.java"`
if [ ! -z $SRC ]; then
javac -classpath $CLASSPATH -d obj $SRC
# stop if compilation fails
if [ $? != 0 ]; then exit; fi
fi
You just need to test if ls has something to list:
ls /var/log/apache2/access.log.* >/dev/null 2>&1 && echo "exists one or more files"
Variation on a theme:
if ls /var/log/apache2/access.log.* >/dev/null 2>&1
then
echo 'At least one file found'
else
echo 'No file found'
fi
ls -1 /var/log/apache2/access.log.* | grep . && echo "One or more files exist."
Or using find
if [ $(find /var/log/apache2/ -type f -name "access.log.*" | wc -l) -gt 0 ]; then
echo "ok"
else
echo "ko"
fi
This condition below doesn't produce stderr. the condition's blackhole (/dev/null) doesn't prevent the stderr in cmd.
if [[ $(ls -1 /var/log/apache2/access.log.* | wc -l ) -gt 0 ]] 2> /dev/null
therefore I suggests this code.
if [[ $(ls -1 /var/log/apache2/access.log.* | wc -l ) -gt 0 ]] 2> /dev/null
then
echo "exists one or more files."
fi
more simplyfied:
if ls /var/log/apache2/access.log.* 2>/dev/null 1>&2; then
echo "ok"
else
echo "ko"
fi