Hide sub directories in while loop - linux

I need to make a script that creates soft/symbolic links, but should also detect if links already exist in the '~/linkedfiles' directory, but the problem is that $file also contains sub directories. ($file will look like this: '~/realfiles/files/file23.gz', but I need 'file23.gz' only.) So my question is, how do I remove the sub directories in $file?
Here is some code as an example:
for file in ~/realfiles/files/*.gz
do
echo "Linking file: $file"
ln -s $file ~/linkedfiles
if [ $? -ne 0 ]; then
echo "[FAIL] Linking of $file failed!"
else
echo "[SUCCESS] $file successfully linked."
fi
done

It appears that you want the base name of the path. There are two ways to do that — the classic reliable way is with the basename command, and the modern not-always-reliable way is with a shell parameter expansion.
for file in ~/realfiles/files/*.gz
do
echo "Linking file: $file"
ln -s "$file" "~/linkedfiles/$(basename "$file")"
if [ $? -ne 0 ]
then echo "[FAIL] Linking of $file failed!"
else echo "[SUCCESS] $file successfully linked."
fi
done
Or:
for file in ~/realfiles/files/*.gz
do
echo "Linking file: $file"
ln -s "$file" "~/linkedfiles/${file##*/}"
if [ $? -ne 0 ]
then echo "[FAIL] Linking of $file failed!"
else echo "[SUCCESS] $file successfully linked."
fi
done
In both scripts, the file ~/realfiles/files/file23.gz will be linked to ~/linkedfiles/file23.gz, which is what I think you are after (though there is room to improve the clarity of the question, such as by citing the desired result for the sample file name).

Slight modification of original script would do
for filename in ~/realfiles/files/*.gz
do
echo "Linking file: $file"
[[ -h "~/linkedfiles/${var##*/}" ]] && continue
ln -s "$filename" ~/linkedfiles/
if [ $? -ne 0 ]
then
echo "[FAIL] Linking of $filename failed!"
else
echo "[SUCCESS] $filename successfully linked."
fi
done
Notes
The -h option with if [ checks ] if the file is a symbolic link
${var##*/} gives you the base name. Check [ parameter expansion ].

targetdir="$HOME/linkedfiles"
for filename in $HOME/realfiles/files/*.gz; do
# if filename is a regular file then ...
if [[ -f "${filename}" ]]; then
# if softlink does not exist in target then link it
if [[ ! -h "${targetdir}/${filename##*/}" ]]; then
echo "Linking ${filename}"
ln -s "${filename}" "${targetdir}"
(( $? == 0 )) && echo 'Link created' || echo 'Create link fails'
else
echo "Skipping because ${filename##*/} exists in ${targetdir}"
fi
fi
done

Related

bash script that checks if file exists [duplicate]

This checks if a file exists:
#!/bin/bash
FILE=$1
if [ -f $FILE ]; then
echo "File $FILE exists."
else
echo "File $FILE does not exist."
fi
How do I only check if the file does not exist?
The test command (written as [ here) has a "not" logical operator, ! (exclamation mark):
if [ ! -f /tmp/foo.txt ]; then
echo "File not found!"
fi
Bash File Testing
-b filename - Block special file
-c filename - Special character file
-d directoryname - Check for directory Existence
-e filename - Check for file existence, regardless of type (node, directory, socket, etc.)
-f filename - Check for regular file existence not a directory
-G filename - Check if file exists and is owned by effective group ID
-G filename set-group-id - True if file exists and is set-group-id
-k filename - Sticky bit
-L filename - Symbolic link
-O filename - True if file exists and is owned by the effective user id
-r filename - Check if file is a readable
-S filename - Check if file is socket
-s filename - Check if file is nonzero size
-u filename - Check if file set-user-id bit is set
-w filename - Check if file is writable
-x filename - Check if file is executable
How to use:
#!/bin/bash
file=./file
if [ -e "$file" ]; then
echo "File exists"
else
echo "File does not exist"
fi
A test expression can be negated by using the ! operator
#!/bin/bash
file=./file
if [ ! -e "$file" ]; then
echo "File does not exist"
else
echo "File exists"
fi
Negate the expression inside test (for which [ is an alias) using !:
#!/bin/bash
FILE=$1
if [ ! -f "$FILE" ]
then
echo "File $FILE does not exist"
fi
The relevant man page is man test or, equivalently, man [ -- or help test or help [ for the built-in bash command.
Alternatively (less commonly used) you can negate the result of test using:
if ! [ -f "$FILE" ]
then
echo "File $FILE does not exist"
fi
That syntax is described in "man 1 bash" in sections "Pipelines" and "Compound Commands".
[[ -f $FILE ]] || printf '%s does not exist!\n' "$FILE"
Also, it's possible that the file is a broken symbolic link, or a non-regular file, like e.g. a socket, device or fifo. For example, to add a check for broken symlinks:
if [[ ! -f $FILE ]]; then
if [[ -L $FILE ]]; then
printf '%s is a broken symlink!\n' "$FILE"
else
printf '%s does not exist!\n' "$FILE"
fi
fi
It's worth mentioning that if you need to execute a single command you can abbreviate
if [ ! -f "$file" ]; then
echo "$file"
fi
to
test -f "$file" || echo "$file"
or
[ -f "$file" ] || echo "$file"
I prefer to do the following one-liner, in POSIX shell compatible format:
$ [ -f "/$DIR/$FILE" ] || echo "$FILE NOT FOUND"
$ [ -f "/$DIR/$FILE" ] && echo "$FILE FOUND"
For a couple of commands, like I would do in a script:
$ [ -f "/$DIR/$FILE" ] || { echo "$FILE NOT FOUND" ; exit 1 ;}
Once I started doing this, I rarely use the fully typed syntax anymore!!
To test file existence, the parameter can be any one of the following:
-e: Returns true if file exists (regular file, directory, or symlink)
-f: Returns true if file exists and is a regular file
-d: Returns true if file exists and is a directory
-h: Returns true if file exists and is a symlink
All the tests below apply to regular files, directories, and symlinks:
-r: Returns true if file exists and is readable
-w: Returns true if file exists and is writable
-x: Returns true if file exists and is executable
-s: Returns true if file exists and has a size > 0
Example script:
#!/bin/bash
FILE=$1
if [ -f "$FILE" ]; then
echo "File $FILE exists"
else
echo "File $FILE does not exist"
fi
You can do this:
[[ ! -f "$FILE" ]] && echo "File doesn't exist"
or
if [[ ! -f "$FILE" ]]; then
echo "File doesn't exist"
fi
If you want to check for file and folder both, then use -e option instead of -f. -e returns true for regular files, directories, socket, character special files, block special files etc.
You should be careful about running test for an unquoted variable, because it might produce unexpected results:
$ [ -f ]
$ echo $?
0
$ [ -f "" ]
$ echo $?
1
The recommendation is usually to have the tested variable surrounded by double quotation marks:
#!/bin/sh
FILE=$1
if [ ! -f "$FILE" ]
then
echo "File $FILE does not exist."
fi
In
[ -f "$file" ]
the [ command does a stat() (not lstat()) system call on the path stored in $file and returns true if that system call succeeds and the type of the file as returned by stat() is "regular".
So if [ -f "$file" ] returns true, you can tell the file does exist and is a regular file or a symlink eventually resolving to a regular file (or at least it was at the time of the stat()).
However if it returns false (or if [ ! -f "$file" ] or ! [ -f "$file" ] return true), there are many different possibilities:
the file doesn't exist
the file exists but is not a regular file (could be a device, fifo, directory, socket...)
the file exists but you don't have search permission to the parent directory
the file exists but that path to access it is too long
the file is a symlink to a regular file, but you don't have search permission to some of the directories involved in the resolution of the symlink.
... any other reason why the stat() system call may fail.
In short, it should be:
if [ -f "$file" ]; then
printf '"%s" is a path to a regular file or symlink to regular file\n' "$file"
elif [ -e "$file" ]; then
printf '"%s" exists but is not a regular file\n' "$file"
elif [ -L "$file" ]; then
printf '"%s" exists, is a symlink but I cannot tell if it eventually resolves to an actual file, regular or not\n' "$file"
else
printf 'I cannot tell if "%s" exists, let alone whether it is a regular file or not\n' "$file"
fi
To know for sure that the file doesn't exist, we'd need the stat() system call to return with an error code of ENOENT (ENOTDIR tells us one of the path components is not a directory is another case where we can tell the file doesn't exist by that path). Unfortunately the [ command doesn't let us know that. It will return false whether the error code is ENOENT, EACCESS (permission denied), ENAMETOOLONG or anything else.
The [ -e "$file" ] test can also be done with ls -Ld -- "$file" > /dev/null. In that case, ls will tell you why the stat() failed, though the information can't easily be used programmatically:
$ file=/var/spool/cron/crontabs/root
$ if [ ! -e "$file" ]; then echo does not exist; fi
does not exist
$ if ! ls -Ld -- "$file" > /dev/null; then echo stat failed; fi
ls: cannot access '/var/spool/cron/crontabs/root': Permission denied
stat failed
At least ls tells me it's not because the file doesn't exist that it fails. It's because it can't tell whether the file exists or not. The [ command just ignored the problem.
With the zsh shell, you can query the error code with the $ERRNO special variable after the failing [ command, and decode that number using the $errnos special array in the zsh/system module:
zmodload zsh/system
ERRNO=0
if [ ! -f "$file" ]; then
err=$ERRNO
case $errnos[err] in
("") echo exists, not a regular file;;
(ENOENT|ENOTDIR)
if [ -L "$file" ]; then
echo broken link
else
echo does not exist
fi;;
(*) syserror -p "can't tell: " "$err"
esac
fi
(beware the $errnos support was broken with some versions of zsh when built with recent versions of gcc).
There are three distinct ways to do this:
Negate the exit status with bash (no other answer has said this):
if ! [ -e "$file" ]; then
echo "file does not exist"
fi
Or:
! [ -e "$file" ] && echo "file does not exist"
Negate the test inside the test command [ (that is the way most answers before have presented):
if [ ! -e "$file" ]; then
echo "file does not exist"
fi
Or:
[ ! -e "$file" ] && echo "file does not exist"
Act on the result of the test being negative (|| instead of &&):
Only:
[ -e "$file" ] || echo "file does not exist"
This looks silly (IMO), don't use it unless your code has to be portable to the Bourne shell (like the /bin/sh of Solaris 10 or earlier) that lacked the pipeline negation operator (!):
if [ -e "$file" ]; then
:
else
echo "file does not exist"
fi
envfile=.env
if [ ! -f "$envfile" ]
then
echo "$envfile does not exist"
exit 1
fi
To reverse a test, use "!".
That is equivalent to the "not" logical operator in other languages. Try this:
if [ ! -f /tmp/foo.txt ];
then
echo "File not found!"
fi
Or written in a slightly different way:
if [ ! -f /tmp/foo.txt ]
then echo "File not found!"
fi
Or you could use:
if ! [ -f /tmp/foo.txt ]
then echo "File not found!"
fi
Or, presing all together:
if ! [ -f /tmp/foo.txt ]; then echo "File not found!"; fi
Which may be written (using then "and" operator: &&) as:
[ ! -f /tmp/foo.txt ] && echo "File not found!"
Which looks shorter like this:
[ -f /tmp/foo.txt ] || echo "File not found!"
The test thing may count too. It worked for me (based on Bash Shell: Check File Exists or Not):
test -e FILENAME && echo "File exists" || echo "File doesn't exist"
This code also working .
#!/bin/bash
FILE=$1
if [ -f $FILE ]; then
echo "File '$FILE' Exists"
else
echo "The File '$FILE' Does Not Exist"
fi
The simplest way
FILE=$1
[ ! -e "${FILE}" ] && echo "does not exist" || echo "exists"
This shell script also works for finding a file in a directory:
echo "enter file"
read -r a
if [ -s /home/trainee02/"$a" ]
then
echo "yes. file is there."
else
echo "sorry. file is not there."
fi
sometimes it may be handy to use && and || operators.
Like in (if you have command "test"):
test -b $FILE && echo File not there!
or
test -b $FILE || echo File there!
If you want to use test instead of [], then you can use ! to get the negation:
if ! test "$FILE"; then
echo "does not exist"
fi
You can also group multiple commands in the one liner
[ -f "filename" ] || ( echo test1 && echo test2 && echo test3 )
or
[ -f "filename" ] || { echo test1 && echo test2 && echo test3 ;}
If filename doesn't exit, the output will be
test1
test2
test3
Note: ( ... ) runs in a subshell, { ... ;} runs in the same shell.

Trying to implement CASE in my shell script

I'm trying to add options to my little safe delete script. For example, I can do ./sdell -s 100 and it will delete files with size above 100 kbs. Anyway, I'm having problems with my safe guard function.
#!/bin/bash
#Purpose = Safe delete
#Created on 20-03-2018
#Version 0.8
#jesus,i'm dumb
#START
##Constants##
dir="/home/cunha/LIXO"
#check to see if the imputs are a files#
for input in "$#"; do
if ! [ -e "$input" ]; then
echo "Input is NOT a file!"
exit 1
fi
done
###main###
case $1 in
-r) echo "test option -r"
;;
*) if [[ -f "$dir/$fwe.tar.bz2" ]]; then
echo "File already exists."
if [[ "$file" -nt "$2" ]]; then
echo "Removing older file." && rm "$dir"/"$fwe.tar.bz2" && tar -czPf "$fwe.tar.bz2" "$(pwd)" && mv "$fwe.tar.bz2" "$d$
fi
else
echo "Ziping it and moving." && tar -czPf "$fwe.tar.bz2" "$(pwd)" && mv "$fwe.tar.bz2" "$dir"
fi
done
;;
esac
The problem is when I call ./sdell -r file1.txt, it says that the input is not a file.
Here is the script without the case, 100% working before having options.
#!/bin/bash
#Purpose = Safe delete
#Created on 20-03-2018
#Version .7
#START
##Constants##
dir="/home/cunha/LIXO"
#check to see if the imputs are a files#
for input in "$#"; do
if ! [ -e "$input" ]; then
echo "Input is NOT a file!"
exit 0
fi
done
###main###
##Cycle FOR so the script accepts multiple file inputs##
for file in "$#"; do
fwe="${file%.*}"
#IF the input file already exist in LIXO#
if [[ -f "$dir/$fwe.tar.bz2" ]]; then
echo "File already exists."
#IF the input file is newer than the file thats already in LIXO#
if [[ "$file" -nt "$2" ]]; then
echo "Removing older file." && rm "$dir"/"$fwe.tar.bz2" && tar -czPf "$fwe.tar.bz2" "$(pwd)" && mv "$fwe.tar.bz2" "$d$
fi
else
echo "Ziping it and moving." && tar -czPf "$fwe.tar.bz2" "$(pwd)" && mv "$fwe.tar.bz2" "$dir"
fi
done
The message you are seeing is unrelated to the case..esac, construct, it is printed by this section:
for input in "$#"; do
if ! [ -e "$input" ]; then
echo "Input is NOT a file!"
exit 1
fi
done
which expands all command-line parameters ($#), including the "-r", and exits the script because "-r" is not a file. The case..esac is never reached. You can run your script with
bash -x file.sh -r test
so that you can see exactly which lines are being executed.
The snippet below probably does what you want, processing all arguments sequentially:
#!/bin/bash
while [ ! -z $1 ]; do
case $1 in
-r) echo "option R"
;;
-f) echo "option F"
;;
*) if [ -f $1 ]; then echo "$1 is a file." ; fi
;;
esac
shift
done
Consider checking if -r has been passed before trying other options and use shift if it was:
#!/usr/bin/env sh
dir="/home/cunha/LIXO"
case $1 in
-r) echo "test option -r"
shift
;;
esac
#check to see if the imputs are a files#
for input in "$#"; do
echo current input: "$input"
if ! [ -e "$input" ]; then
echo "Input $input is NOT a file!"
exit 1
fi
done
if [[ -f "$dir/$fwe.tar.bz2" ]]; then
echo "File already exists."
if [[ "$file" -nt "$2" ]]; then
echo "Removing older file..."
# add stuff
fi
else
echo "Ziping it and moving."
# add stuff
fi

File checking shell script

A program that takes in a file as agument 1 and a time in seconds in argument 2, and then the program will check if:
The file exist
If file has been changed
.
#!/bin/bash
file=$1
sleeptime=$2
bool=true
if [ -e $file ]; then
thetime=$(date -r $file "+%s")
newtime=$(date -r $file "+%s")
while "$bool" = true
do
sleep $sleeptime
newtime=$(date -r $file "+%s")
if [ "$thetime" -ne "$newtime" ]; then
bool=false
echo "Filen $file ble endret"
fi
if [ ! -e $file ]; then
bool=false
echo "Filen $file ble slettet"
fi
done
fi
if [ ! -e $file ]; then
while "$bool" = true
do
sleep $sleeptime
if [ -e $file ]; then
bool=false
echo "Filen $file ble opprettet"
fi
done
fi
Bash is quite hard to get into, to give you a few pointers:
subcommands in conditions are a somewhat tricky, you usually want to run it in a subshell with substitution if the expression contains variables, which is done by $(), not () (explained in this SO question)
while, if needs to be separated from do, then either by a semicolon or by a newline
testing conditions in bash is a bit different from other languages, in your case -f operator checks file is a regular file (see further file testing operators)
it's usually better to quote variables in conditions and variables passed to commands for case they contain special characters (which would be evaluated otherwise)
not sure what should be the nature of "file hasn't been changed" claim, but probably the simplest approach is to test if it's size changed, for a start the -s operator checks if size of a file is more than zero
as it's pointed out in a comment application-specific variables should be lower-case by convention
Your code with these adjustments:
#!/bin/bash
file=$1
sleeptime=$2
while ($(sleep $sleeptime)); do
if [ ! -f "$file" ]; then
touch $file
echo "File $file was created."
elif [ ! -s "$file" ]; then
rm $file
echo "File $file was deleted."
fi
done
That would be something like this:
#!/bin/bash
file=$1
sleeptime=$2
while :; do
sleep "$sleeptime"
if [ -f "$file" ] ; then
if [ "$file" -nt ".tag.$file" ] ; then
echo "Not removed $file, because it was changed"
else
rm "$file"
rm ".tag.$file"
echo "File $file was deleted."
fi
else
touch "$file"
touch ".tag.$file"
echo "File $file was created."
fi
done
Notes:
cleaned-up some of the code.
see man test.
uppercase variables are usually used for environment variables.

Exit a script when there is file with certain name but ignoring a directory with the same name

If a file called “output” already exists, rather than a directory, the script
should display an error and quit.
here is my code so far
for file in *
do
if [ ! -f output ]
then echo "error"
exit 1
fi
done
for file in *; do
if [ "$file" = "output" -a -f "$file" ]; then
echo "error"
exit 1
fi
done
Or
for file in *; do
if [ "$file" = "output" ] && [ -f "$file" ]; then
echo "error"
exit 1
fi
done
And with bash, this one's preferred:
for file in *; do
if [[ $file == output && -f $file ]]; then
echo "error"
exit 1
fi
done
If you want to check if the filename contains the word, not just exactly matches it:
for file in *; do
if [[ $file == *output* && -f $file ]]; then
echo "error"
exit 1
fi
done
Why are we processing every file in the subdirectory? Very Odd.
if [ -f output ]; then
echo "'output exists and is a file"
exit 1
fi
The test command (which is also [) (and is also built-in to most shells (see bash man page too) ), responds with a TRUE response for -f output only when output is a file. You can check if it's a directory with -d.
touch something
if [ -f something ]; then echo "something is a file"; fi
if [ -d something ]; then echo "something is not a file"; fi
rm something
mkdir something
if [ -f something ]; then echo "something is not a subdir"; fi
if [ -d something ]; then echo "something is a subdir"; fi
rmdir something
If you try those commands, you'll get:
something is a file
something is a subdir
No point in iterating through the entire directory contents if you're just looking if a specific file/dir exists.

How to check if a file is empty in Bash?

I have a file called diff.txt. I Want to check whether it is empty.
I wrote a bash script something like below, but I couldn't get it work.
if [ -s diff.txt ]
then
touch empty.txt
rm full.txt
else
touch full.txt
rm emtpy.txt
fi
Misspellings are irritating, aren't they? Check your spelling of empty, but then also try this:
#!/bin/bash -e
if [ -s diff.txt ]; then
# The file is not-empty.
rm -f empty.txt
touch full.txt
else
# The file is empty.
rm -f full.txt
touch empty.txt
fi
I like shell scripting a lot, but one disadvantage of it is that the shell cannot help you when you misspell, whereas a compiler like your C++ compiler can help you.
Notice incidentally that I have swapped the roles of empty.txt and full.txt, as #Matthias suggests.
[ -s file.name ] || echo "file is empty"
[ -s file ] # Checks if file has size greater than 0
[ -s diff.txt ] && echo "file has something" || echo "file is empty"
If needed, this checks all the *.txt files in the current directory; and reports all the empty file:
for file in *.txt; do if [ ! -s $file ]; then echo $file; fi; done
To check if file is empty or has only white spaces, you can use grep:
if [[ -z $(grep '[^[:space:]]' $filename) ]] ; then
echo "Empty file"
...
fi
While the other answers are correct, using the "-s" option will also show the file is empty even if the file does not exist.
By adding this additional check "-f" to see if the file exists first, we ensure the result is correct.
if [ -f diff.txt ]
then
if [ -s diff.txt ]
then
rm -f empty.txt
touch full.txt
else
rm -f full.txt
touch empty.txt
fi
else
echo "File diff.txt does not exist"
fi
Easiest way for checking if file is empty or not:
if [ -s /path-to-file/filename.txt ]
then
echo "File is not empty"
else
echo "File is empty"
fi
You can also write it on single line:
[ -s /path-to-file/filename.txt ] && echo "File is not empty" || echo "File is empty"
#geedoubleya answer is my favorite.
However, I do prefer this
if [[ -f diff.txt && -s diff.txt ]]
then
rm -f empty.txt
touch full.txt
elif [[ -f diff.txt && ! -s diff.txt ]]
then
rm -f full.txt
touch empty.txt
else
echo "File diff.txt does not exist"
fi
[[ -f filename && ! -s filename ]] && echo "filename exists and is empty"
Many of the answers are correct but I feel like they could be more complete
/ simplistic etc. for example :
Example 1 : Basic if statement
# BASH4+ example on Linux :
typeset read_file="/tmp/some-file.txt"
if [ ! -s "${read_file}" ] || [ ! -f "${read_file}" ] ;then
echo "Error: file (${read_file}) not found.. "
exit 7
fi
if $read_file is empty or not there stop the show with exit. More than once I have had misread the top answer here to mean the opposite.
Example 2 : As a function
# -- Check if file is missing /or empty --
# Globals: None
# Arguments: file name
# Returns: Bool
# --
is_file_empty_or_missing() {
[[ ! -f "${1}" || ! -s "${1}" ]] && return 0 || return 1
}
Similar to #noam-manos's grep-based answer, I solved this using cat. For me, -s wasn't working because my "empty" file had >0 bytes.
if [[ ! -z $(cat diff.txt) ]] ; then
echo "diff.txt is not empty"
else
echo "diff.txt is empty"
fi
I came here looking for how to delete empty __init__.py files as they are implicit in Python 3.3+ and ended up using:
find -depth '(' -type f -name __init__.py ')' -print0 |
while IFS= read -d '' -r file; do if [[ ! -s $file ]]; then rm $file; fi; done
Also (at least in zsh) using $path as the variable also breaks your $PATH env and so it'll break your open shell. Anyway, thought I'd share!

Resources