# Copyright (C) 2009, 2014, 2015 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # A collection of shell function definitions used by various build scripts # in the Android NDK (Native Development Kit) # # Get current script name into PROGNAME PROGNAME=`basename $0` # Set package cache directory NDK_CACHE_DIR="/var/tmp/ndk-cache-$USER" if [ ! -d "$NDK_CACHE_DIR" ]; then mkdir -p "$NDK_CACHE_DIR" if [ $? != 0 ]; then echo "Failed to create cache dir $NDK_CACHE_DIR" exit 1 fi fi export TMPDIR=/tmp/ndk-$USER OS=`uname -s` if [ "$OS" == "Darwin" -a -z "$MACOSX_DEPLOYMENT_TARGET" ]; then export MACOSX_DEPLOYMENT_TARGET="10.8" fi # Find the Android NDK root, assuming we are invoked from a script # within its directory structure. # # $1: Variable name that will receive the path # $2: Path of invoking script find_ndk_root () { # Try to auto-detect the NDK root by walking up the directory # path to the current script. local PROGDIR="`dirname \"$2\"`" while [ -n "1" ] ; do if [ -d "$PROGDIR/build/core" ] ; then break fi if [ -z "$PROGDIR" -o "$PROGDIR" = '/' ] ; then return 1 fi PROGDIR="`cd \"$PROGDIR/..\" && pwd`" done eval $1="$PROGDIR" } # Put location of Android NDK into ANDROID_NDK_ROOT and # perform a tiny amount of sanity check # if [ -z "$ANDROID_NDK_ROOT" ] ; then find_ndk_root ANDROID_NDK_ROOT "$0" if [ $? != 0 ]; then echo "Please define ANDROID_NDK_ROOT to point to the root of your" echo "Android NDK installation." exit 1 fi fi echo "$ANDROID_NDK_ROOT" | grep -q -e " " if [ $? = 0 ] ; then echo "ERROR: The Android NDK installation path contains a space !" echo "Please install to a different location." exit 1 fi if [ ! -d $ANDROID_NDK_ROOT ] ; then echo "ERROR: Your ANDROID_NDK_ROOT variable does not point to a directory." exit 1 fi if [ ! -f $ANDROID_NDK_ROOT/build/tools/ndk-common.sh ] ; then echo "ERROR: Your ANDROID_NDK_ROOT variable does not point to a valid directory." exit 1 fi ## Use DRYRUN to find out top-level commands. DRYRUN=${DRYRUN-no} ## Logging support ## VERBOSE=${VERBOSE-yes} # If NDK_LOGFILE is defined in the environment, use this as the log file TMPLOG= if [ -n "$NDK_LOGFILE" ] ; then mkdir -p `dirname "$NDK_LOGFILE"` && touch "$NDK_LOGFILE" TMPLOG="$NDK_LOGFILE" fi # Setup a log file where all log() output will be sent # # $1: log file path (optional) # setup_default_log_file () { if [ -n "$NDK_LOGFILE" ] ; then return fi if [ -n "$1" ] ; then NDK_LOGFILE="$1" else NDK_LOGFILE=$TMPDIR/ndk-log-$$.txt fi export NDK_LOGFILE TMPLOG="$NDK_LOGFILE" rm -rf "$TMPLOG" && mkdir -p `dirname "$TMPLOG"` && touch "$TMPLOG" echo "To follow build in another terminal, please use: tail -F $TMPLOG" } dump () { if [ -n "$TMPLOG" ] ; then echo "$@" >> $TMPLOG fi echo "$@" } dump_n () { if [ -n "$TMPLOG" ] ; then printf %s "$@" >> $TMPLOG fi printf %s "$@" } log () { if [ "$VERBOSE" = "yes" ] ; then echo "$@" else if [ -n "$TMPLOG" ] ; then echo "$@" >> $TMPLOG fi fi } log_n () { if [ "$VERBOSE" = "yes" ] ; then printf %s "$@" else if [ -n "$TMPLOG" ] ; then printf %s "$@" >> $TMPLOG fi fi } run () { if [ "$DRYRUN" = "yes" ] ; then echo "## SKIP COMMAND: $@" elif [ "$VERBOSE" = "yes" ] ; then echo "## COMMAND: $@" "$@" 2>&1 else if [ -n "$TMPLOG" ] ; then echo "## COMMAND: $@" >> $TMPLOG "$@" >>$TMPLOG 2>&1 else "$@" > /dev/null 2>&1 fi fi } panic () { dump "ERROR: $@" exit 1 } fail_panic () { if [ $? != 0 ] ; then dump "ERROR: $@" exit 1 fi } fail_warning () { if [ $? != 0 ] ; then dump "WARNING: $@" fi } ## Utilities ## # Return the value of a given named variable # $1: variable name # # example: # FOO=BAR # BAR=ZOO # echo `var_value $FOO` # will print 'ZOO' # var_value () { # find a better way to do that ? eval echo "$`echo $1`" } # convert to uppercase # assumes tr is installed on the platform ? # to_uppercase () { echo $1 | tr "[:lower:]" "[:upper:]" } ## First, we need to detect the HOST CPU, because proper HOST_ARCH detection ## requires platform-specific tricks. ## HOST_EXE="" HOST_OS=`uname -s` case "$HOST_OS" in Darwin) HOST_OS=darwin ;; Linux) # note that building 32-bit binaries on x86_64 is handled later HOST_OS=linux ;; FreeBsd) # note: this is not tested HOST_OS=freebsd ;; CYGWIN*|*_NT-*) HOST_OS=windows HOST_EXE=.exe if [ "x$OSTYPE" = xcygwin ] ; then HOST_OS=cygwin fi ;; esac #log "HOST_OS=$HOST_OS" #log "HOST_EXE=$HOST_EXE" ## Now find the host architecture. This must correspond to the bitness of ## the binaries we're going to run with this NDK. Certain platforms allow ## you to use a 64-bit kernel with a 32-bit userland, and unfortunately ## commands like 'uname -m' only report the kernel bitness. ## HOST_ARCH=`uname -m` case "$HOST_ARCH" in i?86) HOST_ARCH=x86 # "uname -m" reports i386 on Snow Leopard even though its architecture is # 64-bit. In order to use it to build 64-bit toolchains we need to fix the # reporting anomoly here. if [ "$HOST_OS" = darwin ] ; then if ! echo __LP64__ | (CCOPTS= gcc -E - 2>/dev/null) | grep -q __LP64__ ; then # or if gcc -dM -E - < /dev/null | grep -q __LP64__; then HOST_ARCH=x86_64 fi fi ;; amd64) HOST_ARCH=x86_64 ;; powerpc) HOST_ARCH=ppc ;; esac HOST_FILE_PROGRAM="file" case "$HOST_OS-$HOST_ARCH" in linux-x86_64|darwin-x86_64) ## On Linux or Darwin, a 64-bit kernel doesn't mean that the user-land ## is always 32-bit, so use "file" to determine the bitness of the shell ## that invoked us. The -L option is used to de-reference symlinks. ## ## Note that on Darwin, a single executable can contain both x86 and ## x86_64 machine code, so just look for x86_64 (darwin) or x86-64 (Linux) ## in the output. ## ## Also note that some versions of 'file' in MacPort may report erroneous ## result. See http://b.android.com/53769. Use /usr/bin/file if exists. if [ "$HOST_OS" = "darwin" ]; then SYSTEM_FILE_PROGRAM="/usr/bin/file" test -x "$SYSTEM_FILE_PROGRAM" && HOST_FILE_PROGRAM="$SYSTEM_FILE_PROGRAM" fi "$HOST_FILE_PROGRAM" -L "$SHELL" | grep -q "x86[_-]64" if [ $? != 0 ]; then # $SHELL is not a 64-bit executable, so assume our userland is too. log "Detected 32-bit userland on 64-bit kernel system!" HOST_ARCH=x86 fi ;; esac #log "HOST_ARCH=$HOST_ARCH" # at this point, the supported values for HOST_ARCH are: # x86 # x86_64 # ppc # # other values may be possible but haven't been tested # # at this point, the value of HOST_OS should be one of the following: # linux # darwin # windows (MSys) # cygwin # # Note that cygwin is treated as a special case because it behaves very differently # for a few things. Other values may be possible but have not been tested # # define HOST_TAG as a unique tag used to identify both the host OS and CPU # supported values are: # # linux-x86 # linux-x86_64 # darwin-x86 # darwin-x86_64 # darwin-ppc # windows # windows-x86_64 # # other values are possible but were not tested. # compute_host_tag () { HOST_TAG=${HOST_OS}-${HOST_ARCH} # Special case for windows-x86 => windows case $HOST_TAG in windows-x86|cygwin-x86) HOST_TAG="windows" ;; esac #log "HOST_TAG=$HOST_TAG" } compute_host_tag # Compute the number of host CPU cores an HOST_NUM_CPUS # case "$HOST_OS" in linux) HOST_NUM_CPUS=`cat /proc/cpuinfo | grep processor | wc -l` ;; darwin|freebsd) HOST_NUM_CPUS=`sysctl -n hw.ncpu` ;; windows|cygwin) HOST_NUM_CPUS=$NUMBER_OF_PROCESSORS if [ -z "$HOST_NUM_CPUS" ]; then # In case we're running shell from Cygwin SSH, we have no $NUMBER_OF_PROCESSORS # In such case detect it in another way HOST_NUM_CPUS=`cmd /c "echo %NUMBER_OF_PROCESSORS%" 2>/dev/null | tr -d '\r'` fi ;; *) # let's play safe here HOST_NUM_CPUS=1 esac test -z "$HOST_NUM_CPUS" && HOST_NUM_CPUS=1 test $HOST_NUM_CPUS -lt 1 && HOST_NUM_CPUS=1 #log "HOST_NUM_CPUS=$HOST_NUM_CPUS" # If BUILD_NUM_CPUS is not already defined in your environment, # define it as the double of HOST_NUM_CPUS. This is used to # run Make commands in parralles, as in 'make -j$BUILD_NUM_CPUS' # if [ -z "$BUILD_NUM_CPUS" ] ; then BUILD_NUM_CPUS=`expr $HOST_NUM_CPUS \* 2` fi #log "BUILD_NUM_CPUS=$BUILD_NUM_CPUS" ## HOST TOOLCHAIN SUPPORT ## # force the generation of 32-bit binaries on 64-bit systems # FORCE_32BIT=no force_32bit_binaries () { if [ "$HOST_ARCH" = x86_64 ] ; then log "Forcing generation of 32-bit host binaries on $HOST_ARCH" FORCE_32BIT=yes HOST_ARCH=x86 log "HOST_ARCH=$HOST_ARCH" compute_host_tag fi } # On Windows, cygwin binaries will be generated by default, but # you can force mingw ones that do not link to cygwin.dll if you # call this function. # disable_cygwin () { if [ $HOST_OS = cygwin ] ; then log "Disabling cygwin binaries generation" CFLAGS="$CFLAGS -mno-cygwin" LDFLAGS="$LDFLAGS -mno-cygwin" HOST_OS=windows compute_host_tag fi } # Various probes are going to need to run a small C program mkdir -p $TMPDIR/tmp/tests TMPC=$TMPDIR/tmp/tests/test-$$.c TMPO=$TMPDIR/tmp/tests/test-$$.o TMPE=$TMPDIR/tmp/tests/test-$$$EXE TMPL=$TMPDIR/tmp/tests/test-$$.log # cleanup temporary files clean_temp () { rm -f $TMPC $TMPO $TMPL $TMPE } # cleanup temp files then exit with an error clean_exit () { clean_temp exit 1 } # this function will setup the compiler and linker and check that they work as advertised # note that you should call 'force_32bit_binaries' before this one if you want it to # generate 32-bit binaries on 64-bit systems (that support it). # setup_toolchain () { if [ -z "$CC" ] ; then CC=gcc fi if [ -z "$CXX" ] ; then CXX=g++ fi if [ -z "$CXXFLAGS" ] ; then CXXFLAGS="$CFLAGS" fi if [ -z "$LD" ] ; then LD="$CC" fi log "Using '$CC' as the C compiler" # check that we can compile a trivial C program with this compiler mkdir -p $(dirname "$TMPC") cat > $TMPC < $TMPC < using namespace std; int main() { cout << "Hello World!" << endl; return 0; } EOF compile_cpp if [ $? != 0 ] ; then echo "your C++ compiler doesn't seem to work" cat $TMPL clean_exit fi log "CXX : C++ compiler check ok ($CXX)" # XXX: TODO perform AR checks AR=ar ARFLAGS= } # try to compile the current source file in $TMPC into an object # stores the error log into $TMPL # compile () { log "Object : $CC -o $TMPO -c $CFLAGS $TMPC" $CC -o $TMPO -c $CFLAGS $TMPC 2> $TMPL } compile_cpp () { log "Object : $CXX -o $TMPO -c $CXXFLAGS $TMPC" $CXX -o $TMPO -c $CXXFLAGS $TMPC 2> $TMPL } # try to link the recently built file into an executable. error log in $TMPL # link() { log "Link : $LD -o $TMPE $TMPO $LDFLAGS" $LD -o $TMPE $TMPO $LDFLAGS 2> $TMPL } # run a command # execute() { log "Running: $*" $* } # perform a simple compile / link / run of the source file in $TMPC compile_exec_run() { log "RunExec : $CC -o $TMPE $CFLAGS $TMPC" compile if [ $? != 0 ] ; then echo "Failure to compile test program" cat $TMPC cat $TMPL clean_exit fi link if [ $? != 0 ] ; then echo "Failure to link test program" cat $TMPC echo "------" cat $TMPL clean_exit fi $TMPE } pattern_match () { echo "$2" | grep -q -E -e "$1" } # Let's check that we have a working md5sum here check_md5sum () { A_MD5=`echo "A" | md5sum | cut -d' ' -f1` if [ "$A_MD5" != "bf072e9119077b4e76437a93986787ef" ] ; then echo "Please install md5sum on this machine" exit 2 fi } # Find if a given shell program is available. # We need to take care of the fact that the 'which ' command # may return either an empty string (Linux) or something like # "no in ..." (Darwin). Also, we need to redirect stderr # to /dev/null for Cygwin # # $1: variable name # $2: program name # # Result: set $1 to the full path of the corresponding command # or to the empty/undefined string if not available # find_program () { local PROG RET PROG=`which $2 2>/dev/null` RET=$? if [ $RET != 0 ]; then PROG= fi eval $1=\"$PROG\" return $RET } prepare_download () { find_program CMD_WGET wget find_program CMD_CURL curl find_program CMD_SCRP scp } find_pbzip2 () { if [ -z "$_PBZIP2_initialized" ] ; then find_program PBZIP2 pbzip2 _PBZIP2_initialized="yes" fi } # Download a file with either 'curl', 'wget' or 'scp' # # $1: source URL (e.g. http://foo.com, ssh://blah, /some/path) # $2: target file download_file () { # Is this HTTP, HTTPS or FTP ? if pattern_match "^(http|https|ftp):.*" "$1"; then if [ -n "$CMD_WGET" ] ; then run $CMD_WGET -O $2 $1 elif [ -n "$CMD_CURL" ] ; then run $CMD_CURL -o $2 $1 else echo "Please install wget or curl on this machine" exit 1 fi return fi # Is this SSH ? # Accept both ssh:// or : # if pattern_match "^(ssh|[^:]+):.*" "$1"; then if [ -n "$CMD_SCP" ] ; then scp_src=`echo $1 | sed -e s%ssh://%%g` run $CMD_SCP $scp_src $2 else echo "Please install scp on this machine" exit 1 fi return fi # Is this a file copy ? # Accept both file:// or / # if pattern_match "^(file://|/).*" "$1"; then cp_src=`echo $1 | sed -e s%^file://%%g` run cp -f $cp_src $2 return fi } # Form the relative path between from one abs path to another # # $1 : start path # $2 : end path # # From: # http://stackoverflow.com/questions/2564634/bash-convert-absolute-path-into-relative-path-given-a-current-directory relpath () { [ $# -ge 1 ] && [ $# -le 2 ] || return 1 current="${2:+"$1"}" target="${2:-"$1"}" [ "$target" != . ] || target=/ target="/${target##/}" [ "$current" != . ] || current=/ current="${current:="/"}" current="/${current##/}" appendix="${target##/}" relative='' while appendix="${target#"$current"/}" [ "$current" != '/' ] && [ "$appendix" = "$target" ]; do if [ "$current" = "$appendix" ]; then relative="${relative:-.}" echo "${relative#/}" return 0 fi current="${current%/*}" relative="$relative${relative:+/}.." done relative="$relative${relative:+${appendix:+/}}${appendix#/}" echo "$relative" } # Unpack a given archive # # $1: archive file path # $2: optional target directory (current one if omitted) # unpack_archive () { local ARCHIVE="$1" local DIR=${2-.} local RESULT TARFLAGS ZIPFLAGS mkdir -p "$DIR" TARFLAGS="xpf" # todo: zuav: ZIPFLAGS="-qo" ZIPFLAGS="q" case "$ARCHIVE" in *.zip) (cd $DIR && run unzip $ZIPFLAGS "$ARCHIVE") ;; *.tar) run tar $TARFLAGS "$ARCHIVE" -C $DIR ;; *.tar.gz) run tar z$TARFLAGS "$ARCHIVE" -C $DIR ;; *.tar.bz2) find_pbzip2 if [ -n "$PBZIP2" ] ; then run tar --use-compress-prog=pbzip2 -$TARFLAGS "$ARCHIVE" -C $DIR else run tar j$TARFLAGS "$ARCHIVE" -C $DIR fi # remove ._* files by MacOSX to preserve resource forks we don't need find $DIR -name "\._*" -exec rm {} \; ;; *.tar.xz) run tar J$TARFLAGS "$ARCHIVE" -C $DIR ;; *) panic "Cannot unpack archive with unknown extension: $ARCHIVE" ;; esac } # Pack a given archive # # $1: archive file path (including extension) # $2: source directory for archive content # $3+: list of files (including patterns), all if empty pack_archive () { local ARCHIVE="$1" local SRCDIR="$2" local SRCFILES local TARFLAGS ZIPFLAGS shift; shift; if [ -z "$1" ] ; then SRCFILES="*" else SRCFILES="$@" fi if [ "`basename $ARCHIVE`" = "$ARCHIVE" ] ; then ARCHIVE="`pwd`/$ARCHIVE" fi mkdir -p `dirname $ARCHIVE` TARFLAGS="cf" ZIPFLAGS="-9qr" # Ensure symlinks are stored as is in zip files. for toolchains # this can save up to 7 MB in the size of the final archive #ZIPFLAGS="$ZIPFLAGS --symlinks" case "$ARCHIVE" in *.zip) (cd $SRCDIR && run zip $ZIPFLAGS "$ARCHIVE" $SRCFILES) ;; *.tar) (cd $SRCDIR && run tar $TARFLAGS "$ARCHIVE" $SRCFILES) ;; *.tar.gz) (cd $SRCDIR && run tar z$TARFLAGS "$ARCHIVE" $SRCFILES) ;; *.tar.bz2) find_pbzip2 if [ -n "$PBZIP2" ] ; then (cd $SRCDIR && run tar --use-compress-prog=pbzip2 -$TARFLAGS "$ARCHIVE" $SRCFILES) else (cd $SRCDIR && run tar j$TARFLAGS "$ARCHIVE" $SRCFILES) fi ;; *.tar.xz) (cd $SRCDIR && run tar J$TARFLAGS "$ARCHIVE" $SRCFILES) ;; *) panic "Unsupported archive format: $ARCHIVE" ;; esac } # Copy a directory, create target location if needed # # $1: source directory # $2: target directory location # copy_directory () { local SRCDIR="$1" local DSTDIR="$2" if [ ! -d "$SRCDIR" ] ; then panic "Can't copy from non-directory: $SRCDIR" fi log "Copying directory: " log " from $SRCDIR" log " to $DSTDIR" mkdir -p "$DSTDIR" && (cd "$SRCDIR" && 2>/dev/null tar cf - *) | (tar xf - -C "$DSTDIR") fail_panic "Cannot copy to directory: $DSTDIR" } # Move a directory, create target location if needed # # $1: source directory # $2: target directory location # move_directory () { local SRCDIR="$1" local DSTDIR="$2" if [ ! -d "$SRCDIR" ] ; then panic "Can't move from non-directory: $SRCDIR" fi log "Move directory: " log " from $SRCDIR" log " to $DSTDIR" mkdir -p "$DSTDIR" && (mv "$SRCDIR"/* "$DSTDIR") fail_panic "Cannot move to directory: $DSTDIR" } # This is the same than copy_directory(), but symlinks will be replaced # by the file they actually point to instead. copy_directory_nolinks () { local SRCDIR="$1" local DSTDIR="$2" if [ ! -d "$SRCDIR" ] ; then panic "Can't copy from non-directory: $SRCDIR" fi log "Copying directory (without symlinks): " log " from $SRCDIR" log " to $DSTDIR" mkdir -p "$DSTDIR" && (cd "$SRCDIR" && tar chf - *) | (tar xf - -C "$DSTDIR") fail_panic "Cannot copy to directory: $DSTDIR" } # Copy certain files from one directory to another one # $1: source directory # $2: target directory # $3+: file list (including patterns) copy_file_list () { local SRCDIR="$1" local DSTDIR="$2" shift; shift; if [ ! -d "$SRCDIR" ] ; then panic "Cant' copy from non-directory: $SRCDIR" fi # todo zuav: check for number of arguments? #log "Copying file: $@" log "Copying file: $1 $2 $3 ..." log " from $SRCDIR" log " to $DSTDIR" mkdir -p "$DSTDIR" && (cd "$SRCDIR" && (echo $@ | tr ' ' '\n' | tar cf - -T -)) | (tar xf - -C "$DSTDIR") fail_panic "Cannot copy files to directory: $DSTDIR" } # Rotate a log file # If the given log file exist, add a -1 to the end of the file. # If older log files exist, rename them to - # $1: log file # $2: maximum version to retain [optional] rotate_log () { # Default Maximum versions to retain local MAXVER="5" local LOGFILE="$1" shift; if [ ! -z "$1" ] ; then local tmpmax="$1" shift; tmpmax=`expr $tmpmax + 0` if [ $tmpmax -lt 1 ] ; then panic "Invalid maximum log file versions '$tmpmax' invalid; defaulting to $MAXVER" else MAXVER=$tmpmax; fi fi # Do Nothing if the log file does not exist if [ ! -f "${LOGFILE}" ] ; then return fi # Rename existing older versions ver=$MAXVER while [ $ver -ge 1 ] do local prev=$(( $ver - 1 )) local old="-$prev" # Instead of old version 0; use the original filename if [ $ver -eq 1 ] ; then old="" fi if [ -f "${LOGFILE}${old}" ] ; then mv -f "${LOGFILE}${old}" "${LOGFILE}-${ver}" fi ver=$prev done } # Copy a given package to the cache directory # # $1: package dir # $2: archive file name cache_package () { local package_dir=$1 local archive=$2 log "Copying $package_dir/$archive to cache dir $NDK_CACHE_DIR." cp "$package_dir/$archive" "$NDK_CACHE_DIR" fail_panic "Could not cache package: $archive" } # Copy a given cached package if found to the given package directory # NB # This function will exit a script on succes! # # $1: package dir # $2: archive file name # $3: optional, can be anything, prevents exit try_cached_package () { local package_dir=$1 local archive=$2 if [ "$package_dir" -a -e "$NDK_CACHE_DIR/$archive" ]; then dump "Found cached package: $NDK_CACHE_DIR/$archive" mkdir -p "$package_dir" cp "$NDK_CACHE_DIR/$archive" "$package_dir" fail_panic "Could not copy $archive from cache directory." if [ -n "$3" ]; then return 0 else log "Done" exit 0 fi fi return 1 } # check that every member of the specified list is a member # of the second list # # $1: list of strings separated by blanks # $2: list of standard (permitted) values separated by blanks # $3: error message, optional check_list_values () { #echo "1: $1" #echo "2: $2" #echo "3: $3" local list=$1 local standard_list=$2 local err_msg="bad value" if [ -n "$3" ]; then err_msg=$3 fi local val= local defval= local good= local defsys= for val in $list ; do good="" for defval in $standard_list ; do if [ "$val" = "$defval" ] ; then good="yes" break fi done if [ "$good" != "yes" ] ; then panic "$err_msg: $val" fi done } # Dereference symlink # $1+: directories dereference_symlink () { local DIRECTORY SYMLINKS DIR FILE LINK for DIRECTORY in "$@"; do if [ -d "$DIRECTORY" ]; then while true; do # Find all symlinks in this directory. SYMLINKS=`find $DIRECTORY -type l` if [ -z "$SYMLINKS" ]; then break; fi # Iterate symlinks for SYMLINK in $SYMLINKS; do if [ -L "$SYMLINK" ]; then DIR=`dirname "$SYMLINK"` FILE=`basename "$SYMLINK"` # Note that if `readlink $FILE` is also a link, we want to deal # with it in the next iteration. There is potential infinite-loop # situation for cicular link doesn't exist in our case, though. (cd "$DIR" && \ LINK=`readlink "$FILE"` && \ test ! -L "$LINK" && \ rm -f "$FILE" && \ cp -a "$LINK" "$FILE") fi done done fi done }