How can I detect whether a symlink is broken in Bash? - bash

I run find and iterate through the results with [ \( -L $F \) ] to collect certain symbolic links.
I am wondering if there is an easy way to determine if the link is broken (points to a non-existent file) in this scenario.
Here is my code:
FILES=`find /target/ | grep -v '\.disabled$' | sort`
for F in $FILES; do
if [ -L $F ]; then
DO THINGS
fi
done

# test if symlink is broken (by seeing if it links to an existing file)
if [ ! -e "$F" ] ; then
# code if the symlink is broken
fi

This should print out links that are broken:
find /target/dir -type l ! -exec test -e {} \; -print
You can also chain in operations to find command, e.g. deleting the broken link:
find /target/dir -type l ! -exec test -e {} \; -exec rm {} \;

this will work if the symlink was pointing to a file or a directory, but now is broken
if [[ -L "$strFile" ]] && [[ ! -a "$strFile" ]];then
echo "'$strFile' is a broken symlink";
fi

This finds all files of type "link", which also resolves to a type "link". ie. a broken symlink
find /target -type l -xtype l

If you don't mind traversing non-broken dir symlinks, to find all orphaned links:
$ find -L /target -type l | while read -r file; do echo $file is orphaned; done
To find all files that are not orphaned links:
$ find -L /target ! -type l

What's wrong with:
file $f | grep 'broken symbolic link'

If it does qualify as a symbolic link, but is „not existing“, its a broken link.
if [[ -h $link && ! -e $link ]] ; then
_info "$link is a BROKEN SYMLINK"
fi
REFERENCE

Related

bash if then cp as hardlinks

I would like for the following to hardlink all files to destination, except those directories defined. The find piece is working, but it won't copy any files.
#!/bin/sh
tag_select=$1
source=$3
dest="/backup/"
{
if [[ "$1" = "backup" ]]; then
find . -mindepth 1 -maxdepth 1 ! -name "dir1" ! -name "dir2" | while read line
do
cp -lr "$3" "$dest"
done
fi
}
Please note, I do not want to use rysnc as I would like to create hardlinks in the destination. Thank you in advance!
I guess you know why "$2" doesn't appear anywhere, so we will just presume you are correct. You also understand that every file you find source (e.g. "$3") will be linked to $dest no matter what filenames are discovered by find because you make no use of "$line" that you use as your while read line loop variable. It appears from the question, you want to link all files in source in dest (you must confirm this is your intent) If so, find itself is all you need, e.g.
find source -maxdepth 1 ! -name "dir1" ! -name "dir2" -execdir cp -lr '{}' "$dest" \;
which will find all files (and directories) for 1-level and hardlink each of the files in dest. If that wasn't your intent, please let me know and I'm happy to help further. Your original posts was somewhat an opaque pot of shell stew...
Replace your find command with a simple glob; this also has the benefit of working for any valid file name, not just the ones that don't have newlines in them.
#!/bin/sh
tag_select=$1
source=$3
dest="/backup/"
if [ "$1" = "backup" ]; then
for f in "$source"/*; do
case $f in
dir1|dir2) continue ;;
esac
cp -lr "$f" "$dest"
done
fi
try This
#!/bin/sh
tag_select=$1;
source=$2;
dest="/backup/";
if [ "$1" = "backup" ]; then
find $source -mindepth 1 -maxdepth 1 ! -name "dir1" ! -name "dir2" -exec cp -lr {} "$dest" \;
fi
your command should be
./code.sh backup source_folder_path
example
./code.sh backup ~/Desktop
Try below code for only files in the dir
find $source -maxdepth 1 -type f -exec sh -c "ln -f \"\$(realpath {})\" \"$dest\$(basename {})\"" \;
you cant hard link folders.

IF Statement to Delete Symbolic Link

I am trying to remove an error from appearing on the terminal (it annoys me as it is not one that I need to be worried about).
I have the following code which will check for a broken symbolic link, and if the link is broken it will delete the link:
find /usr/lib/libdb.so -xtype l -delete
How do I change this to a iIF statement?
if [ broken link ] then;
delete file
else
do nothing
fi
Could anyone shed any light on this for me please?
You can use this find:
find /usr/lib/libdb.so -type l -not -exec test -e '{}' \; -print -delete
-not -test -e will detect only broken files(links) and delete them after printing.
To follow your approach, you could try this command:
if [ "`find /usr/lib/libdb.so -type l -xtype l`" != "" ]; then
echo delete file
else
echo do nothing
fi
or, more concisely:
find /usr/lib/libdb.so -type l -xtype l -print -delete
Not tested but give it a try
FILES=`find /usr/lib/libdb.so | grep -v '\.disabled$' | sort`
for F in $FILES; do
if [ -L $F ]; then
if readlink -q $F >/dev/null ; then
delete file
else
DO NOTHING
fi
fi
done

Find path in bash on insensitive manner

Suppose a path like
/home/albfan/Projects/InSaNEWEBproJECT
Despite of the fact to not use such that names. Is there a way to check for a path in an insensitive manner?
I came across to this solution, but I would like to find a builtin or gnu program, if it is possible.
function searchPathInsensitive {
# Replace bar with comma (not valid directory character allowing parse dirs with spaces)
#also remove first / if exist (if not this create a first empty element
ORG="$1"
if [ "${ORG:0:1}" = "/" ]
then
ORG="${ORG:1}"
else
ORG="${PWD:1}/$ORG"
fi
OLDIFS=$IF
IFS=,
for dir in ${ORG//\//,}
do
if [ -z $DIR ]
then
DIR="/$dir"
else
TMP_DIR="$DIR/$dir"
DIR=$(/usr/bin/find $DIR -maxdepth 1 -ipath $TMP_DIR -print -quit)
if [ -z $DIR ]
then
# If some of the path does not exist just copy the element
# exit 1
DIR="$TMP_DIR"
fi
fi
done
IFS=$OLDIFS
echo "$DIR"
}
to use it just do:
(searching on my home)
$ searchPathInsensitive projects/insanewebproject
/home/albfan/Projects/InSaNEWEBproJECT
(inside a project)
$ searchPathInsensitive src/main/java/org/package/webprotocolhttpwrapper.java
/home/albfan/Projects/InSaNEWEBproJECT/src/main/java/org/package/WebProtocolHTTPWrapper.java
$ searchPathInsensitive src/main/resources/logout.png
/home/albfan/Projects/InSaNEWEBproJECT/src/main/resources/LogOut.PNG
I guess the solution is related in any way with find -ipath as all I do with the function is search only for next element in path given on insensitive manner
My fault! I guess I tried
find -ipath 'projects/insanewebproject'
but the trick here is that I must use
find -ipath './projects/insanewebproject'
That ./ does the change. Thanks!.
man says -path is more portable than -wholename
if you expect only one result, you can add | head -n1, cause that way head kill pipe when it fills its buffer, which is only one line length
find -ipath './projects/insanewebproject'| head -n1
The simplest solution:
$ find . | grep -qi /path/to/something[^/]*$
But if you have some additional conditions that must be checked for matched file, you can run grep inside find:
$ find . -exec sh -c 'echo {} | grep -qi /path/to/something' \; -print
Here you will get all files that are in the directory. If you want to get only the directory's name:
$ find . -exec sh -c 'echo {} | grep -qi /path/to/something[^/]*$' \; -print
Example of usage:
$ mkdir -p Projects/InSaNEWEBproJECT/src/main/resources/
$ find . -exec sh -c 'echo {} | grep -qi /projects/insanewebproject[^/]*$' \; -print
./Projects/InSaNEWEBproJECT

Rename file names in current directory and all subdirectories

I have 4 files with the following names in different directories and subdirectories
tag0.txt, tag1.txt, tag2.txt and tag3.txt
and wish to rename them as tag0a.txt, tag1a.txt ,tag2a.txt and tag3a.txt in all directories and subdirectories.
Could anyone help me out using a shell script?
Cheers
$ shopt -s globstar
$ rename -n 's/\.txt$/a\.txt/' **/*.txt
foo/bar/tag2.txt renamed as foo/bar/tag2a.txt
foo/tag1.txt renamed as foo/tag1a.txt
tag0.txt renamed as tag0a.txt
Remove -n to rename after checking the result - It is the "dry run" option.
This can of course be done with find:
find . -name 'tag?.txt' -type f -exec bash -c 'mv "$1" ${1%.*}a.${1##*.}' -- {} \;
Here is a posix shell script (checked with dash):
visitDir() {
local file
for file in "$1"/*; do
if [ -d "$file" ]; then
visitDir "$file";
else
if [ -f "$file" ] && echo "$file"|grep -q '^.*/tag[0-3]\.txt$'; then
newfile=$(echo $file | sed 's/\.txt/a.txt/')
echo mv "$file" "$newfile"
fi
fi
done
}
visitDir .
If you can use bashisms, just replace the inner IF with:
if [[ -f "$file" && "$file" =~ ^.*/tag[0-3]\.txt$ ]]; then
echo mv "$file" "${file/.txt/a.txt}"
fi
First check that the result is what you expected, then possibly remove the "echo" in front of the mv command.
Using the Perl script version of rename that may be on your system:
find . -name 'tag?.txt' -exec rename 's/\.txt$/a$&/' {} \;
Using the binary executable version of rename:
find . -name 'tag?.txt' -exec rename .txt a.txt {} \;
which changes the first occurrence of ".txt". Since the file names are constrained by the -name argument, that won't be a problem.
Is this good enough?
jcomeau#intrepid:/tmp$ find . -name tag?.txt
./a/tag0.txt
./b/tagb.txt
./c/tag1.txt
./c/d/tag3.txt
jcomeau#intrepid:/tmp$ for txtfile in $(find . -name 'tag?.txt'); do \
mv $txtfile ${txtfile%%.txt}a.txt; done
jcomeau#intrepid:/tmp$ find . -name tag*.txt
./a/tag0a.txt
./b/tagba.txt
./c/d/tag3a.txt
./c/tag1a.txt
Don't actually put the backslash into the command, and if you do, expect a '>' prompt on the next line. I didn't put that into the output to avoid confusion, but I didn't want anybody to have to scroll either.

Bash rename extension recursive

I know there are a lot of things like this around, but either they don't work recursively or they are huge.
This is what I got:
find . -name "*.so" -exec mv {} `echo {} | sed s/.so/.dylib/` \;
When I just run the find part it gives me a list of files. When I run the sed part it replaces any .so with .dylib. When I run them together they don't work.
I replaced mv with echo to see what happened:
./AI/Interfaces/C/0.1/libAIInterface.so ./AI/Interfaces/C/0.1/libAIInterface.so
Nothing is replaced at all!
What is wrong?
This will do everything correctly:
find -L . -type f -name "*.so" -print0 | while IFS= read -r -d '' FNAME; do
mv -- "$FNAME" "${FNAME%.so}.dylib"
done
By correctly, we mean:
1) It will rename just the file extension (due to use of ${FNAME%.so}.dylib). All the other solutions using ${X/.so/.dylib} are incorrect as they wrongly rename the first occurrence of .so in the filename (e.g. x.so.so is renamed to x.dylib.so, or worse, ./libraries/libTemp.so-1.9.3/libTemp.so is renamed to ./libraries/libTemp.dylib-1.9.3/libTemp.so - an error).
2) It will handle spaces and any other special characters in filenames (except double quotes).
3) It will not change directories or other special files.
4) It will follow symbolic links into subdirectories and links to target files and rename the target file, not the link itself (the default behaviour of find is to process the symbolic link itself, not the file pointed to by the link).
for X in `find . -name "*.so"`
do
mv $X ${X/.so/.dylib}
done
A bash script to rename file extensions generally
#/bin/bash
find -L . -type f -name '*.'$1 -print0 | while IFS= read -r -d '' file; do
echo "renaming $file to $(basename ${file%.$1}.$2)";
mv -- "$file" "${file%.$1}.$2";
done
Credits to aps2012.
Usage
Create a file e.g. called ext-rename (no extension, so you can run it like a command) in e.g. /usr/bin (make sure /usr/bin is added to your $PATH)
run ext-rename [ext1] [ext2] anywhere in terminal, where [ext1] is renaming from and [ext2] is renaming to. An example use would be: ext-rename so dylib, which will rename any file with extension .so to same name but with extension .dylib.
What is wrong is that
echo {} | sed s/.so/.dylib/
is only executed once, before the find is launched, sed is given {} on its input, which doesn't match /.so/ and is left unchanged, so your resulting command line is
find . -name "*.so" -exec mv {} {}
if you have Bash 4
#!/bin/bash
shopt -s globstar
shopt -s nullglob
for file in /path/**/*.so
do
echo mv "$file" "${file/%.so}.dylib"
done
He needs recursion:
#!/bin/bash
function walk_tree {
local directory="$1"
local i
for i in "$directory"/*;
do
if [ "$i" = . -o "$i" = .. ]; then
continue
elif [ -d "$i" ]; then
walk_tree "$i"
elif [ "${i##*.}" = "so" ]; then
echo mv $i ${i%.*}.dylib
else
continue
fi
done
}
walk_tree "."

Resources