當初寫這個 script,是因為想要保留住瀏覽器所播放過的影音檔,特別是 flash video。瀏覽器播放影音檔時,大多會一邊下載到硬碟,一邊播放。順利的影音檔播放,絕對會需要一個 FIFO 的 buffer,因為這時涉及到兩個非同步的數據存取。而這個 buffer 最直接的形式就是一個磁碟檔案。所以,大部分的瀏覽器所播放的影音檔,多會在硬碟上有一個對應的檔案。
我在 Linux(Debian Etch) 上,一直在使用的 Firefox 或是 Iceweasel,都是把這個檔案放在 /tmp/ 上,例如,/tmp/FlashXXIJOE3B。當瀏覽器確定不再需要它時,例如,該頁面已關閉時,就會把這個檔案 remove 掉。趁著關掉瀏覽器的播放頁面之前,只要把這個檔案改個名字,瀏覽器就去不掉它。也可以趁這個時候把它拷貝起來。所以,我一直都沒有去安裝下載 Flash 檔的軟體或 Plug-in。
然而,一些較新版的瀏覽器,像是 Google Chrome,在建立這個檔案之後,例如,/tmp/FlashXXIJOE3B,就立刻將它 remove 掉。但是因為這個檔案還在使用中,也就是說,它還是一個 opened file,請參見 lsof(1),所以在 UNIX-like 系統上,它其實都還存在,也可以在 Linux 的 proc-filesystem 裡找到,例如,"/proc/19917/fd/4"。
這個 script 就是要把一個檔案已經去掉、但仍開啟的檔案,從 proc-filesystem ,以原來的檔名,連結(symbolic link)到檔案原先所在的位置。使用的方法很簡單,只要把以下的程式碼複製下來,抄到 PATH 裡的任何一個資料夾內,取個名字(我是用 "lndel"),別忘了 "chmod 755 lndel",就可以在需要的時候,隨時把這些檔案「救回來」。只要打 "lndel" 就好了。這個 script 會列出所有它所找到的已 removed 掉的檔案,以及它所建立的連結位置。
它的預設行為是,如果在 /proc/ 之下找到原先檔名開頭為 "Flash" 的已殺掉檔案,就會把它連結到 /tmp/ 下面(symbolic link),因為這就是它被建立時的位置,例如,/tmp/FlashXXIJOE3B。但是所搜尋的檔名,可以用選擇開關 "-k",以 regular expression 的語法指定。基本上,它可以抓到任何已經 removed 掉,但是仍然被某些應用程式開著的檔案,不限檔案的種類。
#!/bin/sh
PROG="`basename $0`"
VERS="v0.2a"
AUTH="Nan-shan Chen"
CDATE="C20110215"
DATE="20110321" # v0.2a 1300644998
OPTS="vqdnk:"
TMP_DATA="/tmp/${PROG}_$$.dat"
KEY="Flash"
F_VERBOSE="false"
F_DEBUG="false"
F_ACTION="true"
F_TEST="false"
usage()
{
echo "$PROG $VERS $AUTH $CDATE/$DATE"
echo "Link Deleted Files from Proc Filesystem to /tmp/"
echo "usage: $PROG [-$OPTS]"
echo ""
echo " -k <str>: RegExpr to search in lsof output (\"$KEY\")"
echo ""
echo " -v: verbose; output informative verbose messages"
echo " -q: quiet; be as quiet as possible with the messages"
echo " -n: no action; show what will be done only if started"
echo ""
echo " FILES: lsof(1) /proc/[0-9]*/fd/[0-9]* ln(1)"
echo " expr(1) grep(1) awk(1) ls(1)"
echo ""
}
TIME_T="`date +%s`"
TIME_S="`date "+%G-%m-%dT%H:%M:%S %z %Z"`"
PROGDIR="`dirname $0`"
ARGV="$*"
while getopts $OPTS optchar
do
case $optchar in
k) KEY="$OPTARG";;
v) F_VERBOSE="true";;
q) F_VERBOSE="false";;
d) F_DEBUG="true";;
n) F_ACTION="false";;
-) shift; break;;
*) usage; exit 1;;
esac
done
shift `expr $OPTIND - 1`
$F_DEBUG && echo >&2 "# $PROG: lsof | grep ${KEY}.*(deleted) > $TMP_DATA"
lsof | grep "${KEY}.*(deleted)" > "$TMP_DATA"
$F_DEBUG && echo >&2 "# $PROG: exec 3< "$TMP_DATA""
exec 3< "$TMP_DATA"
N=0
while read -r LINE 0<&3
do
$F_DEBUG && echo >&2 "# $PROG: got \"$LINE\""
PID=`echo "$LINE" | awk '{print $2 }'`
FD=`echo "$LINE" | awk '{print $4 }' | sed 's/^[^0-9]*\([0-9]*\)[^0-9]*$/\1/'`
ORG=`echo "$LINE" | awk '{print $9 }'`
$F_DEBUG && echo >&2 "# $PROG: PID=$PID FD=$FD ORG=$ORG" # 1298099273
if [ -z "$ORG" ] # 1298099273 temp. sanity check for non-null string
then
echo >&2 "*** $PROG: ORG=\"$ORG\"! aborting..."
exit 255
fi
PROC_FILE="/proc/$PID/fd/$FD"
if [ -r "$PROC_FILE" ]
then
if ls -l | grep -q "$PROC_FILE" # possibly already renamed? 1300644998
then
$F_VERBOSE && echo "# $PROG: $PROC_FILE already linked"
EXISTS=`ls -l | grep "$PROC_FILE" | sed -ne "s/^.*[0-9][0-9]:[0-9][0-9] \(.*\) ->.*$/\1/p"`
$F_VERBOSE && echo -n "# $PROG: " && ls -lL "$EXISTS" # 1300644998
else
N=`expr $N + 1`
$F_VERBOSE && echo "# $PROG: ln -s $PROC_FILE $ORG"
$F_ACTION && ln -s "$PROC_FILE" "$ORG"
echo -n "# $PROG: " && ls -lL "$ORG" # 1300644998
fi
fi
done
$F_DEBUG && echo >&2 "# $PROG: exec 3<&-"
exec 3<&-
$F_VERBOSE && echo "# $PROG: total number of new links = $N" # 1300644998
$F_DEBUG && echo >&2 "# $PROG: rm -f $TMP_DATA"
rm -f "$TMP_DATA"
exit $?
PROG="`basename $0`"
VERS="v0.2a"
AUTH="Nan-shan Chen"
CDATE="C20110215"
DATE="20110321" # v0.2a 1300644998
OPTS="vqdnk:"
TMP_DATA="/tmp/${PROG}_$$.dat"
KEY="Flash"
F_VERBOSE="false"
F_DEBUG="false"
F_ACTION="true"
F_TEST="false"
usage()
{
echo "$PROG $VERS $AUTH $CDATE/$DATE"
echo "Link Deleted Files from Proc Filesystem to /tmp/"
echo "usage: $PROG [-$OPTS]"
echo ""
echo " -k <str>: RegExpr to search in lsof output (\"$KEY\")"
echo ""
echo " -v: verbose; output informative verbose messages"
echo " -q: quiet; be as quiet as possible with the messages"
echo " -n: no action; show what will be done only if started"
echo ""
echo " FILES: lsof(1) /proc/[0-9]*/fd/[0-9]* ln(1)"
echo " expr(1) grep(1) awk(1) ls(1)"
echo ""
}
TIME_T="`date +%s`"
TIME_S="`date "+%G-%m-%dT%H:%M:%S %z %Z"`"
PROGDIR="`dirname $0`"
ARGV="$*"
while getopts $OPTS optchar
do
case $optchar in
k) KEY="$OPTARG";;
v) F_VERBOSE="true";;
q) F_VERBOSE="false";;
d) F_DEBUG="true";;
n) F_ACTION="false";;
-) shift; break;;
*) usage; exit 1;;
esac
done
shift `expr $OPTIND - 1`
$F_DEBUG && echo >&2 "# $PROG: lsof | grep ${KEY}.*(deleted) > $TMP_DATA"
lsof | grep "${KEY}.*(deleted)" > "$TMP_DATA"
$F_DEBUG && echo >&2 "# $PROG: exec 3< "$TMP_DATA""
exec 3< "$TMP_DATA"
N=0
while read -r LINE 0<&3
do
$F_DEBUG && echo >&2 "# $PROG: got \"$LINE\""
PID=`echo "$LINE" | awk '{print $2 }'`
FD=`echo "$LINE" | awk '{print $4 }' | sed 's/^[^0-9]*\([0-9]*\)[^0-9]*$/\1/'`
ORG=`echo "$LINE" | awk '{print $9 }'`
$F_DEBUG && echo >&2 "# $PROG: PID=$PID FD=$FD ORG=$ORG" # 1298099273
if [ -z "$ORG" ] # 1298099273 temp. sanity check for non-null string
then
echo >&2 "*** $PROG: ORG=\"$ORG\"! aborting..."
exit 255
fi
PROC_FILE="/proc/$PID/fd/$FD"
if [ -r "$PROC_FILE" ]
then
if ls -l | grep -q "$PROC_FILE" # possibly already renamed? 1300644998
then
$F_VERBOSE && echo "# $PROG: $PROC_FILE already linked"
EXISTS=`ls -l | grep "$PROC_FILE" | sed -ne "s/^.*[0-9][0-9]:[0-9][0-9] \(.*\) ->.*$/\1/p"`
$F_VERBOSE && echo -n "# $PROG: " && ls -lL "$EXISTS" # 1300644998
else
N=`expr $N + 1`
$F_VERBOSE && echo "# $PROG: ln -s $PROC_FILE $ORG"
$F_ACTION && ln -s "$PROC_FILE" "$ORG"
echo -n "# $PROG: " && ls -lL "$ORG" # 1300644998
fi
fi
done
$F_DEBUG && echo >&2 "# $PROG: exec 3<&-"
exec 3<&-
$F_VERBOSE && echo "# $PROG: total number of new links = $N" # 1300644998
$F_DEBUG && echo >&2 "# $PROG: rm -f $TMP_DATA"
rm -f "$TMP_DATA"
exit $?
External Links
[1] Liorithiel's blog, getting flash videos from almost deleted files, 2010-11-08
http://liori.jogger.pl/2010/11/08/getting-flash-videos-from-almost-deleted-files/