[实践OK]linux shell 中"2>&1"含义的指针理解,如何把错误定向到空文件?

jackxiang 2008-9-19 18:04 | |

实践:
ls AlTest1.err > log   #有此文件,也就是输出指向屏幕给输入到了log文件

ls AlTest1.err2 > log #没有此文件,输出的是错误2,正常输出的1指向屏幕1----->log (1指向log),2也是指向幕,于是将错输出到了屏。
ls: AlTest1.err2: No such file or directory

ls AlTest1.err2 > log 2>&1 # (2指向1,而1指向log,因此2也指向了log)错误也指向了文件,于是此次的错误没有输出。
cat log
ls: AlTest1.err2: No such file or directory

2>&1 换个位置的情况,如下:
ls AlTest1.err2  2>&1 > log  #输出:ls: AlTest1.err2: No such file or directory ,原因是2还是指向屏幕
本来1----->屏幕 (1指向屏幕)
执行2>&1后, 2----->1 (2指向1,而1指向屏幕,因此2也指向了屏幕)
执行>log后, 1----->log (1指向log,2还是指向屏幕)

如果文件存在,1输出指向了log,所以下成这个语句是能输出到文件中的:
ls AlTest1.err  2>&1 > log
cat log
AlTest1.err

将上述的正确输出的文件换成/dev/null, 即可实现正确错误均给导入空洞。

来自:https://www.jb51.net/article/169778.htm


0:标准输出,1:标准输入,2:标准错误。 输出可以输出到控制台,也可输出到文件,从左向右的顺序,这样可理解后面思考题。
2>&1表明将文件描述2(标准错误输出)的内容重定向到文件描述符1(标准输出),为什么1前面需要&?当没有&时,1会被认为是一个普通的文件,有&表示重定向的目标不是一个文件,而是一个文件描述符。在前面我们知道,test.sh >log.txt又将文件描述符1的内容重定向到了文件log.txt,那么最终标准错误也会重定向到log.txt。
cat test.sh


#./test.sh > log.txt
./test.sh:行6: whatthis: 未找到命令
./test.sh:行6: whatthis: 未找到命令
./test.sh:行6: whatthis: 未找到命令




如果写成这样:#./test.sh > log.txt 2>&1 ,标准输出和错误输出均指向了 /data/codesdev/testdemo/shell/log.txt:
#ll
总用量 0
lrwx------ 1 root root 64 10月 30 16:38 0 -> /dev/pts/2
l-wx------ 1 root root 64 10月 30 16:38 1 -> /data/codesdev/testdemo/shell/log.txt
l-wx------ 1 root root 64 10月 30 16:37 2 -> /data/codesdev/testdemo/shell/log.txt
lr-x------ 1 root root 64 10月 30 16:38 255 -> /data/codesdev/testdemo/shell/test.sh
w
xiangdon pts/2    202.108.16.78    15:37   47.00s  0.10s  0.03s sshd: xiangdong [priv] #程序运行终端
xiangdon pts/3    202.108.16.78    15:40    0.00s  0.08s  0.03s sshd: xiangdong [priv]  #另一个终端
pkill -KILL -t pts/3

cd /proc/28725/fd



我们总结一下前面的内容:

程序运行后会打开三个文件描述符,分别是标准输入,标准输出和标准错误输出。
在调用脚本时,可使用2>&1来将标准错误输出重定向。
只需要查看脚本的错误时,可将标准输出重定向到文件,而标准错误会打印在控制台,便于查看。
>>log.txt会将重定向内容追加到log.txt文件末尾。
通过查看/proc/进程id/fd下的内容,可了解进程打开的文件描述符信息。
来自:https://mp.weixin.qq.com/s/-9uO7lc_xfvpZxEsaez7HQ

#./test.sh 2>&1 >log.txt  #思考题:为何这样一样有错误输出呢?
思考
下面的调用会将标准错误输出重定向到文件中吗?为什么?
./test.sh 2>&1 >log.txt

./test.sh:行6: whatthis: 未找到命令
./test.sh:行6: whatthis: 未找到命令
./test.sh:行6: whatthis: 未找到命令
./test.sh:行6: whatthis: 未找到命令
./test.sh:行6: whatthis: 未找到命令
./test.sh:行6: whatthis: 未找到命令
原因2 -> /dev/pts/2,并没有给指向/data/codesdev/testdemo/shell/log.txt:
#ll /proc/29313/fd
总用量 0
lrwx------ 1 root root 64 10月 30 16:44 0 -> /dev/pts/2
l-wx------ 1 root root 64 10月 30 16:44 1 -> /data/codesdev/testdemo/shell/log.txt
lrwx------ 1 root root 64 10月 30 16:44 2 -> /dev/pts/2
lr-x------ 1 root root 64 10月 30 16:44 255 -> /data/codesdev/testdemo/shell/test.sh

答案:解释一下思考题:不能。
因为从左往右结合,现有2>&1,这个时候1指向的是控制台,这个时候也就将标准错误2重定向控制台(相当于不起作用),最后才将标准输出重定向到log.txt。结果就是只有标准输出重定向到了log.txt。可以通过同样的方法查看文件描述符的指向。
从左往右结合:也就是说先有输出(2&>1:标准错误输出到文件1输出)到控制台(不起作用,此时输出是控制台。),然后再定向到log.txt文件,而不是先输出到Log文件,之前是test.sh >log.txt将文件描述符1的内容重定向到了文件log.txt,那么最终标准错误也会重定向到log.txt,现在是直接将错误给输出到终端了。
> 就是1,也就是说将标准输出到一个文件,它就是1,而./test.sh 2>&1 >log.txt ,的1是输出到默认的终端terminal了,而./test.sh >log.txt 2>&1里的输出1到文件了,后面的1也就跟着到了log.txt文件。
===============================================================================================

find -name "*.jpg" | awk -F "/" '{print "curl -F userfile=@/data1/app4_data/" $2 "/" $3 "
http://app.space.sina.com.cn/upload.php" " >> /home/xiangdong2/image_logs/" $3 ".txt"}' | sh
这个脚本放入:push_upload_img.sh
中的时候出现:curl传输速度等的信息,我们就要用到:>/dev/null  2>&1  & 了

sh push_upload_img.sh >/dev/null 2>&1 &





详细解释:
脚本是:
      nohup /mnt/Nand3/H2000G  >/dev/null  2>&1  &
      对于& 1 更准确的说应该是文件描述符 1,而1 一般代表的就是STDOUT_FILENO,实际上这个操作就是一个dup2(2)调用.他标准输出到all_result ,然后复制标准输出到文件描述符2(STDERR_FILENO),其后果就是文件描述符1和2指向同一个文件表项,也可以说错误的输出被合并了.其中0表示键盘输入 1表示屏幕输出 2表示错误输出.把标准出错重定向到标准输出,然后扔到/DEV/NULL下面去。通俗的说,就是把所有标准输出和标准出错都扔到垃圾桶里面。
      command >out.file  2>&1 &
      command >out.file是将command的输出重定向到out.file文件,即输出内容不打印到屏幕上,而是输出到out.file文件中。2>&1 是将标准出错重定向到标准输出,这里的标准输出已经重定向到了out.file文件,即将标准出错也输出到out.file文件中。最后一个& , 是让该命令在后台执行。
      
      试想2>1代表什么,2与>结合代表错误重定向,而1则代表错误重定向到一个文件1,而不代表标准输出;
换成2>&1,&与1结合就代表标准输出了,就变成错误重定向到标准输出.

      你可以用
            ls 2>1测试一下,不会报没有2文件的错误,但会输出一个空的文件1;
            ls xxx 2>1测试,没有xxx这个文件的错误输出到了1中;
            ls xxx 2>&1测试,不会生成1这个文件了,不过错误跑到标准输出了;
            ls xxx >out.txt 2>&1, 实际上可换成 ls xxx 1>out.txt 2>&1;重定向符号>默认是1,错误和输出都传到out.txt了。
      为何2>&1要写在后面?
      command > file 2>&1
       首先是command > file将标准输出重定向到file中, 2>&1 是标准错误拷贝了标准输出的行为,也就是同样被重定向到file中,最终结果就是标准输出和错误都被重定向到file中。
      command 2>&1 >file
      2>&1 标准错误拷贝了标准输出的行为,但此时标准输出还是在终端。>file 后输出才被重定向到file,但标准错误仍然保持在终端。

用strace可以看到:
1. command > file 2>&1
这个命令中实现重定向的关键系统调用序列是:
open(file) == 3
dup2(3,1)
dup2(1,2)

2. command 2>&1 >file
这个命令中实现重定向的关键系统调用序列是:
dup2(1,2)
open(file) == 3
dup2(3,1)

      可以考虑一下不同的dup2()调用序列会产生怎样的文件共享结构。请参考APUE 3.10, 3.12
command > filename 把把标准输出重定向到一个新文件中
command >> filename 把把标准输出重定向到一个文件中(追加)
command 1 > fielname 把把标准输出重定向到一个文件中
command > filename 2>&1 把把标准输出和标准错误一起重定向到一个文件中
command 2 > filename 把把标准错误重定向到一个文件中
command 2 >> filename 把把标准输出重定向到一个文件中(追加)
command >> filename 2>&1 把把标准输出和标准错误一起重定向到一个文件中(追加)
command < filename >filename2 把command命令以filename文件作为标准输入,以filename2文件作为标准输出
command < filename 把command命令以filename文件作为标准输入
command << delimiter 把从标准输入中读入,直至遇到delimiter分界符
command <&m 把把文件描述符m作为标准输入
command >&m 把把标准输出重定向到文件描述符m中
command <&- 把关闭标准输入


crontab实例:

*/30 * * * *    /opt/bin/php /opt/www/daemon/search/rsyncuserdata.php > /dev/null 2>&1
*/30 * * * *    /opt/bin/php /opt/www/daemon/search/makeuserdata.php > /dev/null 2>&1
0 */1 * * *     /opt/bin/php /opt/www/daemon/search/makeusernewdata.php > /dev/null 2>&1
0 0 */1 * *     /opt/bin/php /opt/www/daemon/birthday/birthday.php > /dev/null 2>&1


#cut weblog by yunfeng@20080627
55 */1 * * *  /usr/bin/perl /usr/home/pro/scripts/getlog_space.pl >/dev/null  2>&1

#gzip the yesterday's weblog by yunfeng@20080627
40 0 * * *  /usr/home/pro/scripts/gzip_log.sh >/dev/null  2>&1
#rsync weblog to info sys and product data third times by yunfeng@20080627
#30 1 * * *  /usr/bin/perl /usr/home/pro/scripts/trans.pl >/dev/null  2>&1
#40 1 * * *  /usr/bin/perl /usr/home/pro/scripts/trans.pl >/dev/null  2>&1
#50 1 * * *  /usr/bin/perl /usr/home/pro/scripts/trans.pl >/dev/null  2>&1

#delete old curl cookies by yunfeng@20080627
18 */1 * * * /usr/bin/find /tmp/cookie/ -type f -mmin +60 -exec rm {} \;



談到 I/O redirection ,不妨先讓我們認識一下 File Descriptor (FD) 。

程式的運算,在大部份情況下都是進行數據(data)的處理,
這些數據從哪讀進?又,送出到哪裡呢?
這就是 file descriptor (FD) 的功用了。

在 shell 程式中,最常使用的 FD 大概有三個,分別為:
0: Standard Input (STDIN)
1: Standard Output (STDOUT)
2: Standard Error Output (STDERR)

在標準情況下,這些 FD 分別跟如下設備(device)關聯:
stdin(0): keyboard
stdout(1): monitor
stderr(2): monitor

我們可以用如下下命令測試一下:
$ mail -s test root
this is a test mail.
please skip.
^d (同時按 crtl 跟 d 鍵)
很明顯,mail 程式所讀進的數據,就是從 stdin 也就是 keyboard 讀進的。
不過,不見得每個程式的 stdin 都跟 mail 一樣從 keyboard 讀進,
因為程式作者可以從檔案參數讀進 stdin ,如:
$ cat /etc/passwd

但,要是 cat 之後沒有檔案參數則又如何呢?
哦,請您自己玩玩看囉....  ^_^
$ cat

(請留意數據輸出到哪裡去了,最後別忘了按 ^d 離開...)
至於 stdout 與 stderr ,嗯.
讓我們繼續看 stderr 好了。
事實上,stderr 沒甚麼難理解的:說穿了就是"錯誤信息"要往哪邊送而已...
比方說,若讀進的檔案參數是不存在的,那我們在 monitor 上就看到了:
$ ls no.such.file
ls: no.such.file: No such file or directory

若,一個命令同時產生 stdout 與 stderr 呢?
那還不簡單,都送到 monitor 來就好了:
$ touch my.file
$ ls my.file no.such.file
ls: no.such.file: No such file or directory
my.file

okay,至此,關於 FD 及其名稱、還有相關聯的設備,相信你已經沒問題了吧?
那好,接下來讓我們看看如何改變這些 FD 的預設數據通道,
我們可用 < 來改變讀進的數據通道(stdin),使之從指定的檔案讀進。
我們可用 > 來改變送出的數據通道(stdout, stderr),使之輸出到指定的檔案。

比方說:
$ cat < my.file
就是從 my.file 讀進數據
$ mail -s test root < /etc/passwd
則是從 /etc/passwd 讀進...
這樣一來,stdin 將不再是從 keyboard 讀進,而是從檔案讀進了...
嚴格來說,< 符號之前需要指定一個 FD 的(之間不能有空白),
但因為 0 是 < 的預設值,因此 < 與 0< 是一樣的﹗

okay,這個好理解吧?
那,要是用兩個 << 又是啥呢?
這是所謂的 HERE Document ,它可以讓我們輸入一段文本,直到讀到 << 後指定的字串。
比方說:
$ cat <<FINISH
first line here
second line there
third line nowhere
FINISH
這樣的話,cat 會讀進 3 行句子,而無需從 keyboard 讀進數據且要等 ^d 結束輸入。
當你搞懂了 0< 原來就是改變 stdin 的數據輸入通道之後,相信要理解如下兩個 redirection 就不難了:
* 1>
* 2>
前者是改變 stdout 的數據輸出通道,後者是改變 stderr 的數據輸出通道。
兩者都是將原本要送出到 monitor 的數據轉向輸出到指定檔案去。
由於 1 是 > 的預設值,因此,1> 與 > 是相同的,都是改 stdout 。

用上次的 ls 例子來說明一下好了:

$ ls my.file no.such.file 1>file.out
ls: no.such.file: No such file or directory
這樣 monitor 就只剩下 stderr 而已。因為 stdout 給寫進 file.out 去了。

$ ls my.file no.such.file 2>file.err
my.file
這樣 monitor 就只剩下 stdout ,因為 stderr 寫進了 file.err 。

$ ls my.file no.such.file 1>file.out 2>file.err
這樣 monitor 就啥也沒有,因為 stdout 與 stderr 都給轉到檔案去了...

呵~~~ 看來要理解 > 一點也不難啦﹗是不?沒騙你吧? ^_^
不過,有些地方還是要注意一下的。
首先,是 file locking 的問題。比方如下這個例子:

$ ls my.file no.such.file 1>file.both 2>file.both
從 file system 的角度來說,單一檔案在單一時間內,只能被單一的 FD 作寫入。
假如 stdout(1) 與 stderr(2) 都同時在寫入 file.both 的話,
則要看它們在寫入時否碰到同時競爭的情形了,基本上是"先搶先贏"的原則。
讓我們用周星馳式的"慢鏡頭"來看一下 stdout 與 stderr 同時寫入 file.out 的情形好了:
* 第 1, 2, 3 秒為 stdout 寫入
* 第 3, 4, 5 秒為 stderr 寫入
那麼,這時候 stderr 的第 3 秒所寫的數據就丟失掉了﹗
要是我們能控制 stderr 必須等 stdout 寫完再寫,或倒過來,stdout 等 stderr 寫完再寫,那問題就能解決。
但從技術上,較難掌控的,尤其是 FD 在作"長期性"的寫入時...

那,如何解決呢?所謂山不轉路轉、路不轉人轉嘛,
我們可以換一個思維:將 stderr 導進 stdout 或將 stdout 導進 sterr ,而不是大家在搶同一份檔案,不就行了﹗
bingo﹗就是這樣啦:
* 2>&1 就是將 stderr 併進 stdout 作輸出
* 1>&2 或 >&2 就是將 stdout 併進 stderr 作輸出
於是,前面的錯誤操作可以改為:
$ ls my.file no.such.file 1>file.both 2>&1

$ ls my.file no.such.file 2>file.both >&2

這樣,不就皆大歡喜了嗎? 呵~~~ ^_^

不過,光解決了 locking 的問題還不夠,我們還有其他技巧需要了解的。
學佛的最高境界,就是"四大皆空"。至於是空哪四大塊?我也不知,因為我還沒到那境界...
但這個"空"字,卻非常值得我們返複把玩的:
--- 色即是空、空即是色﹗
好了,施主要是能夠領會"空"的禪意,那離修成正果不遠矣~~~  

在 Linux 檔案系統裡,有個設備檔位於 /dev/null 。
許多人都問過我那是甚麼玩意兒?我跟你說好了:那就是"空"啦﹗
沒錯﹗空空如也的空就是 null 了.... 請問施主是否忽然有所頓誤了呢?然則恭喜了~~~ ^_^

這個 null 在 I/O Redirection 中可有用得很呢:
* 若將 FD1 跟 FD2 轉到 /dev/null 去,就可將 stdout 與 stderr 弄不見掉。
* 若將 FD0 接到 /dev/null 來,那就是讀進 nothing 。
比方說,當我們在執行一個程式時,畫面會同時送出 stdout 跟 stderr ,
假如你不想看到 stderr (也不想存到檔案去),那可以:
$ ls my.file no.such.file 2>/dev/null
my.file

若要相反:只想看到 stderr 呢?還不簡單﹗將 stdout 弄到 null 就行:
$ ls my.file no.such.file >/dev/null
ls: no.such.file: No such file or directory

那接下來,假如單純只跑程式,不想看到任何輸出結果呢?
哦,這裡留了一手上次節目沒講的法子,專門贈予有緣人﹗... ^_^
除了用 >/dev/null 2>&1 之外,你還可以如此:
$ ls my.file no.such.file &>/dev/null
(提示:將 &> 換成 >& 也行啦~~! )

okay?講完佛,接下來,再讓我們看看如下情況:
$ echo "1" > file.out
$ cat file.out
1
$ echo "2" > file.out
$ cat file.out
2
看來,我們在重導 stdout 或 stderr 進一份檔案時,似乎永遠只獲得最後一次導入的結果。
那,之前的內容呢?
呵~~~ 要解決這個問提很簡單啦,將 > 換成 >> 就好:
$ echo "3" >> file.out
$ cat file.out
2
3
如此一來,被重導的目標檔案之內容並不會失去,而新的內容則一直增加在最後面去。
easy ? 呵 ...  ^_^

但,只要你再一次用回單一的 > 來重導的話,那麼,舊的內容還是會被"洗"掉的﹗
這時,你要如何避免呢?
----備份﹗ yes ,我聽到了﹗不過.... 還有更好的嗎?
既然與施主這麼有緣份,老納就送你一個錦囊妙法吧:
$ set -o noclobber
$ echo "4" > file.out
-bash: file: cannot overwrite existing file

那,要如何取消這個"限制"呢?
哦,將 set -o 換成 set +o 就行:
$ set +o noclobber
$ echo "5" > file.out
$ cat file.out
5

再問:那... 有辦法不取消而又"臨時"蓋寫目標檔案嗎?
哦,佛曰:不可告也﹗
啊~~~ 開玩笑的、開玩笑的啦~~~  ^_^  唉,早就料到人心是不足的了﹗
$ set -o noclobber
$ echo "6" >| file.out
$ cat file.out
6
留意到沒有:在 > 後面再加個" | "就好(注意: > 與 | 之間不能有空白哦)....

呼.... (深呼吸吐納一下吧)~~~  ^_^
再來還有一個難題要你去參透的呢:
$ echo "some text here" > file
$ cat < file
some text here
$ cat < file > file.bak
$ cat < file.bak
some text here
$ cat < file > file
$ cat < file

嗯?﹗注意到沒有?﹗﹗
---- 怎麼最後那個 cat 命令看到的 file 竟是空的?﹗
why? why? why?
前面提到:$ cat < file > file 之後原本有內容的檔案結果卻被洗掉了﹗
要理解這一現像其實不難,這只是 priority 的問題而已:
* 在 IO Redirection 中,stdout 與 stderr 的管道會先準備好,才會從 stdin 讀進資料。
也就是說,在上例中,> file 會先將 file 清空,然後才讀進 < file ,
但這時候檔案已經被清空了,因此就變成讀不進任何資料了...

哦~~~ 原來如此~~~~  ^_^
那... 如下兩例又如何呢?
$ cat <> file
$ cat < file >> file

嗯... 同學們,這兩個答案就當練習題囉,下節課之前請交作業﹗

好了,I/O Redirection 也快講完了,sorry,因為我也只知道這麼多而已啦~~~ 嘻~~  ^_^
不過,還有一樣東東是一定要講的,各位觀眾(請自行配樂~!#@!$%) :
---- 就是 pipe line 也﹗

談到 pipe line ,我相信不少人都不會陌生:
我們在很多 command line 上常看到的" | "符號就是  pipe line 了。
不過,究竟 pipe line 是甚麼東東呢?
別急別急... 先查一下英漢字典,看看 pipe 是甚麼意思?
沒錯﹗它就是"水管"的意思...
那麼,你能想像一下水管是怎麼一根接著一根的嗎?
又,每根水管之間的 input 跟 output 又如何呢?
嗯??
靈光一閃:原來 pipe line 的 I/O 跟水管的 I/O 是一模一樣的:
* 上一個命令的 stdout 接到下一個命令的 stdin 去了﹗
的確如此... 不管在 command line 上你使用了多少個 pipe line ,
前後兩個 command 的 I/O 都是彼此連接的﹗(恭喜:你終於開竅了﹗  ^_^ )

不過... 然而... 但是...  ... stderr 呢?
好問題﹗不過也容易理解:
* 若水管漏水怎麼辦?
也就是說:在 pipe line 之間,前一個命令的 stderr 是不會接進下一命令的 stdin 的,
其輸出,若不用 2> 導到 file 去的話,它還是送到監視器上面來﹗
這點請你在 pipe line 運用上務必要注意的。

那,或許你又會問:
* 有辦法將 stderr 也餵進下一個命令的 stdin 去嗎?
(貪得無厭的家夥﹗)
方法當然是有,而且你早已學過了﹗  ^_^
我提示一下就好:
* 請問你如何將 stderr 合併進 stdout 一同輸出呢?
若你答不出來,下課之後再來問我吧... (如果你臉皮真夠厚的話...)

或許,你仍意尤未盡﹗或許,你曾經碰到過下面的問題:
* 在 cm1 | cm2 | cm3  ... 這段 pipe line 中,若要將 cm2 的結果存到某一檔案呢?

若你寫成 cm1 |  cm2 > file | cm3 的話,
那你肯定會發現 cm3 的 stdin 是空的﹗(當然啦,你都將水管接到別的水池了﹗)
聰明的你或許會如此解決:
cm1 | cm2 > file ; cm3 < file
是的,你的確可以這樣做,但最大的壞處是:這樣一來,file I/O 會變雙倍﹗
在 command 執行的整個過程中,file I/O 是最常見的最大效能殺手。
凡是有經驗的 shell 操作者,都會盡量避免或降低 file I/O 的頻率。

那,上面問題還有更好方法嗎?
有的,那就是 tee 命令了。
* 所謂 tee 命令是在不影響原本 I/O 的情況下,將 stdout 複製一份到檔案去。
因此,上面的命令行可以如此打:
cm1 | cm2 | tee file | cm3
在預設上,tee 會改寫目標檔案,若你要改為增加內容的話,那可用 -a 參數達成。

基本上,pipe line 的應用在 shell 操作上是非常廣泛的,尤其是在 text filtering 方面,
凡舉 cat, more, head, tail, wc, expand, tr, grep, sed, awk, ... 等等文字處理工具,
搭配起 pipe line 來使用,你會驚覺 command line 原來是活得如此精彩的﹗
常讓人有"眾裡尋他千百度,驀然回首,那人卻在燈火闌珊處﹗"之感...  ^_^

....

好了,關於 I/O Redirection 的介紹就到此告一段落。
若日後有空的話,再為大家介紹其它在 shell 上好玩的東西﹗bye...  ^_^
俺谈谈:>是覆盖,所以先将文件清空
        >>是追加,所以先找文件的尾巴哦,在尾巴后面写
不知道明白否

作者:jackxiang@向东博客 专注WEB应用 构架之美 --- 构架之美,在于尽态极妍 | 应用之美,在于药到病除
地址:https://jackxiang.com/post/1264/
版权所有。转载时必须以链接形式注明作者和原始出处及本声明!


最后编辑: jackxiang 编辑于2022-8-5 17:53
评论列表
发表评论

昵称

网址

电邮

打开HTML 打开UBB 打开表情 隐藏 记住我 [登入] [注册]