mirror of
https://github.com/linux-odyssey/linux-odyssey.git
synced 2026-11-05 19:20:12 +08:00
wip: migrate backend to new game package
This commit is contained in:
1 parent
1d843f7355
commit
1616f62755
94 files changed
+263
-3055
No files matched your search
@@ -1,4 +1,5 @@
|
|||||||
config
|
config
|
||||||
|
.quest
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
"@fortawesome/vue-fontawesome": "^3.0.3",
|
"@fortawesome/vue-fontawesome": "^3.0.3",
|
||||||
"@linux-odyssey/constants": "*",
|
"@linux-odyssey/constants": "*",
|
||||||
"@linux-odyssey/file-graph": "*",
|
"@linux-odyssey/file-graph": "*",
|
||||||
|
"@linux-odyssey/game": "*",
|
||||||
"@linux-odyssey/models": "*",
|
"@linux-odyssey/models": "*",
|
||||||
"@linux-odyssey/utils": "*",
|
"@linux-odyssey/utils": "*",
|
||||||
"@trpc/client": "^11.1.0",
|
"@trpc/client": "^11.1.0",
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ const right = () => {
|
|||||||
current.value += 1
|
current.value += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const response = computed(() => {
|
||||||
|
return sessionStore.session?.responses[current.value]
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -69,21 +73,19 @@ const right = () => {
|
|||||||
<div id="hint" class="bg-bg p-8 overflow-y-auto">
|
<div id="hint" class="bg-bg p-8 overflow-y-auto">
|
||||||
<ul>
|
<ul>
|
||||||
<li
|
<li
|
||||||
v-for="response in sessionStore.session?.responses[current]"
|
v-if="response"
|
||||||
:key="response.type"
|
:key="response.type"
|
||||||
class="text-text font-xl whitespace-pre-wrap"
|
class="text-text font-xl whitespace-pre-wrap"
|
||||||
>
|
>
|
||||||
<div v-for="content in response.content" :key="content">
|
<MarkdownText :content="response.content" />
|
||||||
<MarkdownText :content="content" />
|
|
||||||
</div>
|
|
||||||
</li>
|
</li>
|
||||||
<li
|
<!-- <li
|
||||||
v-for="hint in sessionStore.session?.hints[current]"
|
v-for="hint in response?.hints"
|
||||||
:key="hint"
|
:key="hint"
|
||||||
class="text-text-primary font-xl whitespace-pre-wrap"
|
class="text-text-primary font-xl whitespace-pre-wrap"
|
||||||
>
|
>
|
||||||
<MarkdownText :content="hint" />
|
<MarkdownText :content="hint" />
|
||||||
</li>
|
</li> -->
|
||||||
</ul>
|
</ul>
|
||||||
<br />
|
<br />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,11 +2,10 @@
|
|||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import MarkdownText from '../MarkdownText.vue'
|
import MarkdownText from '../MarkdownText.vue'
|
||||||
import useSession from '../../store/session'
|
import useSession from '../../store/session'
|
||||||
import { Task } from '../../types'
|
|
||||||
|
|
||||||
const sessionStore = useSession()
|
const sessionStore = useSession()
|
||||||
const tasks = computed(() => {
|
const tasks = computed(() => {
|
||||||
return sessionStore.session?.tasks.map((task: Task) => {
|
return sessionStore.session?.tasks.map((task) => {
|
||||||
const prefix = task.completed ? '✓' : '➤'
|
const prefix = task.completed ? '✓' : '➤'
|
||||||
const color = task.completed ? 'text-text-primary' : 'text-text'
|
const color = task.completed ? 'text-text-primary' : 'text-text'
|
||||||
return {
|
return {
|
||||||
|
|||||||
+36
-19
@@ -1,16 +1,23 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { FileGraph, FileObject } from '@linux-odyssey/file-graph'
|
import { FileGraph, FileObject } from '@linux-odyssey/file-graph'
|
||||||
import { QuestDetailResponse } from '../../../packages/constants/dist/src'
|
import { IResponse, ITask } from '@linux-odyssey/game'
|
||||||
import { createSession, getActiveSession } from '../utils/api'
|
import { createSession, getActiveSession } from '../utils/api'
|
||||||
import Socket from '../utils/socket'
|
import Socket from '../utils/socket'
|
||||||
import SocketTerminal from '../utils/terminal'
|
import SocketTerminal from '../utils/terminal'
|
||||||
import { Session, StageResponse } from '../types'
|
import { Session } from '../types'
|
||||||
import { trpc } from '../utils/trpc'
|
import { trpc } from '../utils/trpc'
|
||||||
|
|
||||||
const socket = new Socket()
|
const socket = new Socket()
|
||||||
const term = new SocketTerminal(40, 80)
|
const term = new SocketTerminal(40, 80)
|
||||||
let hasSetup = false
|
let hasSetup = false
|
||||||
|
|
||||||
|
interface QuestDetailResponse {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
instruction: string
|
||||||
|
requirements: string[]
|
||||||
|
}
|
||||||
|
|
||||||
interface Store {
|
interface Store {
|
||||||
session: Session | null
|
session: Session | null
|
||||||
questId: string
|
questId: string
|
||||||
@@ -30,7 +37,7 @@ const useSession = defineStore('session', {
|
|||||||
},
|
},
|
||||||
async setSession(session: Session) {
|
async setSession(session: Session) {
|
||||||
this.session = session
|
this.session = session
|
||||||
this.session.graph = new FileGraph(session.graph)
|
// this.session.graph = new FileGraph(session.graph)
|
||||||
term.reset()
|
term.reset()
|
||||||
await socket.connect(session)
|
await socket.connect(session)
|
||||||
term.focus()
|
term.focus()
|
||||||
@@ -55,22 +62,29 @@ const useSession = defineStore('session', {
|
|||||||
this.session.pwd = event.pwd
|
this.session.pwd = event.pwd
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
newResponse(response: StageResponse) {
|
newResponse(responses: IResponse[]) {
|
||||||
if (!this.session) return
|
if (!this.session) return
|
||||||
this.session.responses.push(response.responses)
|
this.session.responses = responses
|
||||||
this.session.hints.push(response.hints)
|
// this.session.hints.push(response.hints)
|
||||||
if (response.tasks) {
|
// if (response.tasks) {
|
||||||
this.session.tasks = response.tasks
|
// this.session.tasks = response.tasks
|
||||||
}
|
// }
|
||||||
if (
|
// if (
|
||||||
response.status === 'finished' &&
|
// response.status === 'finished' &&
|
||||||
this.session.status !== 'finished'
|
// this.session.status !== 'finished'
|
||||||
) {
|
// ) {
|
||||||
this.finish()
|
// this.finish()
|
||||||
}
|
// }
|
||||||
this.session.status = response.status
|
// this.session.status = response.status
|
||||||
|
},
|
||||||
|
newTask(tasks: ITask[]) {
|
||||||
|
if (!this.session) return
|
||||||
|
this.session.tasks = tasks
|
||||||
|
},
|
||||||
|
finish() {
|
||||||
|
if (!this.session) return
|
||||||
|
this.session.status = 'finished'
|
||||||
},
|
},
|
||||||
finish() {},
|
|
||||||
reset() {
|
reset() {
|
||||||
socket.reset()
|
socket.reset()
|
||||||
term.reset()
|
term.reset()
|
||||||
@@ -87,8 +101,11 @@ const useSession = defineStore('session', {
|
|||||||
socket.on('graph', (event: { discover: FileObject[]; pwd: string }) => {
|
socket.on('graph', (event: { discover: FileObject[]; pwd: string }) => {
|
||||||
this.updateGraph(event)
|
this.updateGraph(event)
|
||||||
})
|
})
|
||||||
socket.on('response', (response: StageResponse) => {
|
socket.on('response', (responses: IResponse[]) => {
|
||||||
this.newResponse(response)
|
this.newResponse(responses)
|
||||||
|
})
|
||||||
|
socket.on('task', (tasks: ITask[]) => {
|
||||||
|
this.newTask(tasks)
|
||||||
})
|
})
|
||||||
hasSetup = true
|
hasSetup = true
|
||||||
},
|
},
|
||||||
|
|||||||
+3
-4
@@ -1,6 +1,5 @@
|
|||||||
import { FileGraph } from '@linux-odyssey/file-graph'
|
import { FileGraph } from '@linux-odyssey/file-graph'
|
||||||
// eslint-disable-next-line import/no-relative-packages
|
import { IResponse, ITask } from '@linux-odyssey/game'
|
||||||
import { IResponse } from '@linux-odyssey/models'
|
|
||||||
|
|
||||||
export interface Task {
|
export interface Task {
|
||||||
id: number
|
id: number
|
||||||
@@ -21,6 +20,6 @@ export interface Session {
|
|||||||
graph: FileGraph
|
graph: FileGraph
|
||||||
pwd: string
|
pwd: string
|
||||||
hints: string[][]
|
hints: string[][]
|
||||||
tasks: Task[]
|
tasks: ITask[]
|
||||||
responses: IResponse[][]
|
responses: IResponse[]
|
||||||
}
|
}
|
||||||
@@ -84,7 +84,7 @@ onMounted(async () => {
|
|||||||
<div class="bg-border rounded-xl h-[90%] p-2">
|
<div class="bg-border rounded-xl h-[90%] p-2">
|
||||||
<div class="bg-bg rounded-lg h-full p-2">
|
<div class="bg-bg rounded-lg h-full p-2">
|
||||||
<section id="visualization" class="bg-bg-primary h-full rounded-md">
|
<section id="visualization" class="bg-bg-primary h-full rounded-md">
|
||||||
<VisualizationPart />
|
<!-- <VisualizationPart /> -->
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import validator from 'validator'
|
import validator from 'validator'
|
||||||
import { isValidUsername } from '@linux-odyssey/utils'
|
import { isValidUsername } from '@linux-odyssey/utils'
|
||||||
import { passwordPolicy } from '../../../packages/constants/dist/src'
|
import { passwordPolicy } from '@linux-odyssey/constants'
|
||||||
import AuthForm from '../components/AuthForm.vue'
|
import AuthForm from '../components/AuthForm.vue'
|
||||||
import DynamicBackground from '../components/DynamicBackground.vue'
|
import DynamicBackground from '../components/DynamicBackground.vue'
|
||||||
import HeaderPart from '../components/header/HeaderPart.vue'
|
import HeaderPart from '../components/header/HeaderPart.vue'
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export const questSchema = z.object({
|
|||||||
title: z.string(),
|
title: z.string(),
|
||||||
image: z.string(),
|
image: z.string(),
|
||||||
instruction: z.string(),
|
instruction: z.string(),
|
||||||
requirements: requirementsSchema,
|
requirements: requirementsSchema.default([]),
|
||||||
stages: z.array(stageSchema),
|
stages: z.array(stageSchema),
|
||||||
exceptions: z.array(globalExceptionSchema).optional(),
|
exceptions: z.array(globalExceptionSchema).optional(),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
|
||||||
export const responseSchema = z.object({
|
export const responseSchema = z.object({
|
||||||
type: z.enum(['narrative', 'dialogue']),
|
type: z.enum(['narrative', 'dialogue']).default('narrative'),
|
||||||
content: z.string(),
|
content: z.string(),
|
||||||
hint: z.string().optional(),
|
hint: z.string().optional(),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ import { z } from 'zod'
|
|||||||
import { conditionSchema } from './condition.js'
|
import { conditionSchema } from './condition.js'
|
||||||
import { responseSchema } from './response.js'
|
import { responseSchema } from './response.js'
|
||||||
import { stageExceptionSchema } from './exception.js'
|
import { stageExceptionSchema } from './exception.js'
|
||||||
|
import { requirementsSchema } from './requirements.js'
|
||||||
|
|
||||||
export const stageSchema = z.object({
|
export const stageSchema = z.object({
|
||||||
id: z.string().regex(/^[a-zA-Z0-9_-]+$/),
|
id: z.string().regex(/^[a-zA-Z0-9_-]+$/),
|
||||||
name: z.string().optional(),
|
name: z.string().optional(),
|
||||||
requirements: z.array(z.string()),
|
requirements: requirementsSchema.default([]),
|
||||||
condition: conditionSchema,
|
condition: conditionSchema,
|
||||||
response: responseSchema,
|
response: responseSchema,
|
||||||
exceptions: stageExceptionSchema.array().optional(),
|
exceptions: stageExceptionSchema.array().optional(),
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export interface ISession {
|
|||||||
updatedAt: Date
|
updatedAt: Date
|
||||||
finishedAt?: Date
|
finishedAt?: Date
|
||||||
lastActivityAt: Date
|
lastActivityAt: Date
|
||||||
stages: string[]
|
completedEvents: string[]
|
||||||
graph: INode
|
graph: INode
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ export const Session = model<ISession>(
|
|||||||
default: Date.now,
|
default: Date.now,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
stages: [String],
|
completedEvents: [String],
|
||||||
graph: {
|
graph: {
|
||||||
type: nodeSchema,
|
type: nodeSchema,
|
||||||
default: {
|
default: {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
interface Node {
|
interface Node {
|
||||||
_id: string
|
id: string
|
||||||
requirements: string[]
|
requirements: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ export default class DAG {
|
|||||||
private layers: number[] = []
|
private layers: number[] = []
|
||||||
|
|
||||||
constructor(nodes: Node[]) {
|
constructor(nodes: Node[]) {
|
||||||
this.nodes = new Map(nodes.map((node) => [node._id, node]))
|
this.nodes = new Map(nodes.map((node) => [node.id, node]))
|
||||||
this.edges = new Set<[string, string]>()
|
this.edges = new Set<[string, string]>()
|
||||||
this.nodes.forEach((_, id) => {
|
this.nodes.forEach((_, id) => {
|
||||||
this.setLayer(id)
|
this.setLayer(id)
|
||||||
|
|||||||
+37
-40
@@ -3,63 +3,60 @@ instruction: |
|
|||||||
在這個關卡中,你將會學到:
|
在這個關卡中,你將會學到:
|
||||||
1. 如何使用 `echo` 在終端機上顯示文字。
|
1. 如何使用 `echo` 在終端機上顯示文字。
|
||||||
|
|
||||||
requirements:
|
# requirements:
|
||||||
- get-started
|
# - get-started
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- id: start
|
- id: start
|
||||||
task: 按下 `開始` 按鈕開始
|
name: 按下 `開始` 按鈕開始
|
||||||
condition:
|
condition:
|
||||||
command:
|
command: ^echo start$
|
||||||
- ^echo start$
|
response:
|
||||||
responses:
|
content: |
|
||||||
- content: |
|
還記得第一關使用的指令 `echo` 嗎?
|
||||||
還記得第一關使用的指令 `echo` 嗎?
|
`echo` 指令可以將後方接續的文字輸出到終端機上。
|
||||||
`echo` 指令可以將後方接續的文字輸出到終端機上。
|
例如你輸入 `echo Hello`,終端機就會顯示 `Hello`。
|
||||||
例如你輸入 `echo Hello`,終端機就會顯示 `Hello`。
|
你可以試著輸入 `echo ` 並在後面加上一些文字,然後按下 Enter 鍵,就可以看到終端機上顯示了你剛才輸入的文字。
|
||||||
你可以試著輸入 `echo ` 並在後面加上一些文字,然後按下 Enter 鍵,就可以看到終端機上顯示了你剛才輸入的文字。
|
|
||||||
hints:
|
hints:
|
||||||
- ➤ 輸入 `echo` 並在後面加上一些文字
|
- ➤ 輸入 `echo` 並在後面加上一些文字
|
||||||
- ➤ 例如:`echo example`
|
- ➤ 例如:`echo example`
|
||||||
|
|
||||||
- id: echo
|
- id: echo
|
||||||
task: 在終端機上顯示顯示任意文字
|
name: 在終端機上顯示顯示任意文字
|
||||||
requirements:
|
requirements:
|
||||||
- start
|
- start
|
||||||
condition:
|
condition:
|
||||||
command:
|
command: ^echo .*
|
||||||
- ^echo .*
|
response:
|
||||||
responses:
|
content: |
|
||||||
- content:
|
你應該可以看到終端機上顯示了你在 `echo` 指令後面的文字。
|
||||||
- 你應該可以看到終端機上顯示了你在 `echo` 指令後面的文字。
|
接下來,請你試著在終端機上顯示 `How are you?`。
|
||||||
- 接下來,請你試著在終端機上顯示 `How are you?`。
|
|
||||||
hints:
|
hints:
|
||||||
- ➤ 利用 `echo` 指令來在終端機上顯示 `How are you?`
|
- ➤ 利用 `echo` 指令來在終端機上顯示 `How are you?`
|
||||||
|
|
||||||
- id: END
|
- id: END
|
||||||
task: 在終端機上顯示 `How are you?`
|
name: 在終端機上顯示 `How are you?`
|
||||||
requirements:
|
requirements:
|
||||||
- echo
|
- echo
|
||||||
condition:
|
condition:
|
||||||
command:
|
command: ^echo [Hh]ow are you\??$
|
||||||
- ^echo [Hh]ow are you\??$
|
|
||||||
exceptions:
|
exceptions:
|
||||||
- condition:
|
- id: missing_echo
|
||||||
command:
|
condition:
|
||||||
- ^[Hh]ow are you\??$
|
command: ^[Hh]ow are you\??$
|
||||||
responses:
|
response:
|
||||||
- type: narrative
|
type: narrative
|
||||||
content:
|
content: |
|
||||||
- 在 `How are you?` 前面必須加上 `echo`
|
在 `How are you?` 前面必須加上 `echo`
|
||||||
- condition:
|
- id: incomplete_command
|
||||||
command:
|
condition:
|
||||||
- ^echo$
|
command: ^echo$
|
||||||
responses:
|
response:
|
||||||
- type: narrative
|
type: narrative
|
||||||
content:
|
content: |
|
||||||
- 在 `echo ` 後面還需要加上 `How are you?`
|
在 `echo ` 後面還需要加上 `How are you?`
|
||||||
responses:
|
response:
|
||||||
- content:
|
content: |
|
||||||
- 你成功的在終端機上顯示了 `How are you?`。
|
你成功的在終端機上顯示了 `How are you?`。
|
||||||
- 相信你已經了解如何使用 `echo` 指令來顯示文字了。
|
相信你已經了解如何使用 `echo` 指令來顯示文字了。
|
||||||
- 之後如果要在終端機顯示文字,記得可以使用 `echo` 指令。
|
之後如果要在終端機顯示文字,記得可以使用 `echo` 指令。
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
echo start
|
|
||||||
echo next
|
|
||||||
ls
|
|
||||||
cd folder03
|
|
||||||
pwd
|
|
||||||
cd ../folder02
|
|
||||||
ls ../folder01
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
title: 測驗一
|
|
||||||
instruction: |
|
|
||||||
在這個關卡中,將會考驗你是否理解以下內容:
|
|
||||||
1. `echo`:用來輸出文字
|
|
||||||
2. `ls`:用來查看資料夾的內容
|
|
||||||
3. `cd`:用來在資料夾之間移動
|
|
||||||
4. `pwd`:用來顯示目前的路徑
|
|
||||||
|
|
||||||
requirements:
|
|
||||||
- path
|
|
||||||
|
|
||||||
stages:
|
|
||||||
- id: start
|
|
||||||
task: 按下 `開始` 按鈕開始
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^echo start$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
這個關卡將會測試你是否理解之前的教學內容。
|
|
||||||
因此不會告訴你要輸入什麼指令。
|
|
||||||
請思考並應用正確的指令來完成任務。
|
|
||||||
|
|
||||||
現在,請讓終端機輸出 `next` 來繼續。
|
|
||||||
|
|
||||||
- id: next
|
|
||||||
task: 讓終端機輸出 `next`
|
|
||||||
requirements:
|
|
||||||
- start
|
|
||||||
condition:
|
|
||||||
output:
|
|
||||||
- ^next$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
現在請你查看目前資料夾的內容。
|
|
||||||
|
|
||||||
- id: ls-~
|
|
||||||
task: 查看目前資料夾的內容
|
|
||||||
requirements:
|
|
||||||
- next
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
command:
|
|
||||||
- ^ls$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
請進入 `folder03` 資料夾。
|
|
||||||
|
|
||||||
- id: cd-folder03
|
|
||||||
task: 進入 `folder03` 資料夾
|
|
||||||
requirements:
|
|
||||||
- ls-~
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander/folder03$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
請在終端機顯示目前的路徑。
|
|
||||||
|
|
||||||
- id: pwd-folder03
|
|
||||||
task: 顯示目前的路徑
|
|
||||||
requirements:
|
|
||||||
- cd-folder03
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^pwd$
|
|
||||||
output:
|
|
||||||
- ^/home/commander/folder03$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
現在請你進入 `folder02` 資料夾,
|
|
||||||
並查看 `folder02` 資料夾的內容。
|
|
||||||
|
|
||||||
- id: ls-folder02
|
|
||||||
task: 進入並查看 `folder02` 資料夾的內容
|
|
||||||
requirements:
|
|
||||||
- pwd-folder03
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander/folder02$
|
|
||||||
command:
|
|
||||||
- ^ls$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
最後,請你留在 `folder02` 資料夾,
|
|
||||||
並查看 `folder01` 資料夾的內容。
|
|
||||||
|
|
||||||
- id: END
|
|
||||||
requirements:
|
|
||||||
- ls-folder02
|
|
||||||
task: 留在 `folder02` 資料夾,查看 `folder01` 資料夾的內容。
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander/folder02$
|
|
||||||
command:
|
|
||||||
- ^ls
|
|
||||||
output:
|
|
||||||
- file1.txt
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
恭喜你通過了這個測驗!
|
|
||||||
你可以繼續學習之後的教學內容了。
|
|
||||||
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
@@ -1,7 +0,0 @@
|
|||||||
echo start
|
|
||||||
echo next
|
|
||||||
ls
|
|
||||||
cat hello.txt
|
|
||||||
cat pneumonoultramicroscopicsilicovolcanoconiosis.txt
|
|
||||||
mkdir folder
|
|
||||||
touch folder/file.txt
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
title: 測驗二
|
|
||||||
instruction: |
|
|
||||||
在這個關卡中,將會考驗你是否理解以下內容:
|
|
||||||
1. `cat`:用來查看檔案的內容
|
|
||||||
2. `touch`:用來建立檔案
|
|
||||||
3. `mkdir`:用來建立資料夾
|
|
||||||
4. 用 `tab` 鍵來自動補全
|
|
||||||
5. 之前測驗包含的內容
|
|
||||||
|
|
||||||
requirements:
|
|
||||||
- create
|
|
||||||
|
|
||||||
stages:
|
|
||||||
- id: start
|
|
||||||
task: 按下 `開始` 按鈕開始
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^echo start$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
這個關卡將會測試你是否理解之前的教學內容。
|
|
||||||
因此不會告訴你要輸入什麼指令。
|
|
||||||
請思考並應用正確的指令來完成任務。
|
|
||||||
|
|
||||||
現在,請讓終端機輸出 `next` 來繼續。
|
|
||||||
|
|
||||||
- id: next
|
|
||||||
task: 讓終端機輸出 `next`
|
|
||||||
requirements:
|
|
||||||
- start
|
|
||||||
condition:
|
|
||||||
output:
|
|
||||||
- ^next$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
現在請你查看目前資料夾的內容。
|
|
||||||
|
|
||||||
- id: ls-~
|
|
||||||
task: 查看目前資料夾的內容
|
|
||||||
requirements:
|
|
||||||
- next
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
command:
|
|
||||||
- ^ls$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
請查看 `hello.txt` 檔案的內容。
|
|
||||||
|
|
||||||
- id: cat-hello
|
|
||||||
task: 查看 `hello.txt` 檔案內容
|
|
||||||
requirements:
|
|
||||||
- ls-~
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^cat hello.txt$
|
|
||||||
output:
|
|
||||||
- ^This is `hello.txt`.$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
請查看另一個有著很長檔名的檔案內容。
|
|
||||||
|
|
||||||
- id: cat-long-filename
|
|
||||||
task: 查看另一個檔案的內容
|
|
||||||
requirements:
|
|
||||||
- cat-hello
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^cat pneumonoultramicroscopicsilicovolcanoconiosis.txt$
|
|
||||||
output:
|
|
||||||
- ^A name that has been invented for a lung disease caused by breathing in very small pieces of ash or dust.$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
現在請你建立一個名稱為 `folder` 的資料夾。
|
|
||||||
|
|
||||||
- id: mkdir-folder
|
|
||||||
task: 建立名稱為 `folder` 的資料夾
|
|
||||||
requirements:
|
|
||||||
- cat-long-filename
|
|
||||||
condition:
|
|
||||||
files:
|
|
||||||
- path: /home/commander/folder
|
|
||||||
type: folder
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
最後,請你在 `folder` 資料夾中,
|
|
||||||
建立檔名為 `file.txt` 的檔案。
|
|
||||||
|
|
||||||
- id: END
|
|
||||||
requirements:
|
|
||||||
- mkdir-folder
|
|
||||||
task: 在 `folder` 中建立檔名為 `file.txt` 的檔案
|
|
||||||
condition:
|
|
||||||
files:
|
|
||||||
- path: /home/commander/folder/file.txt
|
|
||||||
type: file
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
恭喜你通過了這個測驗!
|
|
||||||
你可以繼續學習之後的教學內容了。
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
This is `hello.txt`.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
A name that has been invented for a lung disease caused by breathing in very small pieces of ash or dust.
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
echo start
|
|
||||||
echo next
|
|
||||||
ls
|
|
||||||
cd folder
|
|
||||||
cp hello.txt world.txt
|
|
||||||
rm hello.txt
|
|
||||||
mv world.txt ..
|
|
||||||
cd ..
|
|
||||||
mv world.txt hello.txt
|
|
||||||
cat hello.txt
|
|
||||||
rmdir folder
|
|
||||||
rm -r trash_can
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
title: 測驗三
|
|
||||||
instruction: |
|
|
||||||
在這個關卡中,將會考驗你是否理解以下內容:
|
|
||||||
1. `cp`:用來複製檔案和資料夾
|
|
||||||
2. `mv`:用來移動檔案和資料夾或更改名稱
|
|
||||||
3. `rm`:用來刪除檔案和資料夾
|
|
||||||
4. `rmdir`:用來刪除空的資料夾
|
|
||||||
5. 透過選項讓指令達成不同的功能,例如 `rm -r`
|
|
||||||
6. 之前測驗包含的內容
|
|
||||||
|
|
||||||
requirements:
|
|
||||||
- remove
|
|
||||||
|
|
||||||
stages:
|
|
||||||
- id: start
|
|
||||||
task: 按下 `開始` 按鈕開始
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^echo start$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
這個關卡將會測試你是否理解之前的教學內容。
|
|
||||||
因此不會告訴你要輸入什麼指令。
|
|
||||||
請思考並應用正確的指令來完成任務。
|
|
||||||
|
|
||||||
現在,請讓終端機輸出 `next` 來繼續。
|
|
||||||
|
|
||||||
- id: next
|
|
||||||
task: 讓終端機輸出 `next`
|
|
||||||
requirements:
|
|
||||||
- start
|
|
||||||
condition:
|
|
||||||
output:
|
|
||||||
- ^next$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
現在請你查看目前資料夾的內容。
|
|
||||||
|
|
||||||
- id: ls-~
|
|
||||||
task: 查看目前資料夾的內容
|
|
||||||
requirements:
|
|
||||||
- next
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
command:
|
|
||||||
- ^ls$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
請複製 `folder` 資料夾中的檔案到 `folder` 中,並命名為 `world.txt` 。
|
|
||||||
|
|
||||||
- id: cp-hello-world
|
|
||||||
task: 複製 `folder` 中的檔案並命名為 `world.txt`
|
|
||||||
requirements:
|
|
||||||
- ls-~
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^cp .*hello.txt .*world.txt$
|
|
||||||
files:
|
|
||||||
- path: /home/commander/folder/hello.txt
|
|
||||||
type: file
|
|
||||||
- path: /home/commander/folder/world.txt
|
|
||||||
type: file
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
請刪除 `hello.txt` 檔案。
|
|
||||||
|
|
||||||
- id: rm-hello
|
|
||||||
task: 刪除 `hello.txt`
|
|
||||||
requirements:
|
|
||||||
- cp-hello-world
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^rm .*hello.txt$
|
|
||||||
files:
|
|
||||||
- path: /home/commander/folder/hello.txt
|
|
||||||
type: file
|
|
||||||
exists: false
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
現在請將 `world.txt` 移出 `folder` 資料夾。
|
|
||||||
(移到 `commander` 資料夾中)
|
|
||||||
|
|
||||||
- id: mv-world-..
|
|
||||||
task: 將 `world.txt` 移出 `folder` 資料夾
|
|
||||||
requirements:
|
|
||||||
- rm-hello
|
|
||||||
condition:
|
|
||||||
files:
|
|
||||||
- path: /home/commander/folder/world.txt
|
|
||||||
type: file
|
|
||||||
exists: false
|
|
||||||
- path: /home/commander/world.txt
|
|
||||||
type: file
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
將 `world.txt` 的名稱改回 `hello.txt` ,
|
|
||||||
並查看檔案的內容。
|
|
||||||
|
|
||||||
- id: cat-hello
|
|
||||||
task: 將 `world.txt` 的名稱改回 `hello.txt` 後查看檔案內容
|
|
||||||
requirements:
|
|
||||||
- mv-world-..
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^cat hello.txt$
|
|
||||||
files:
|
|
||||||
- path: /home/commander/world.txt
|
|
||||||
type: file
|
|
||||||
exists: false
|
|
||||||
- path: /home/commander/hello.txt
|
|
||||||
type: file
|
|
||||||
output:
|
|
||||||
- ^This is `hello.txt`.$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
刪除 `folder` 資料夾。
|
|
||||||
|
|
||||||
- id: rmdir-folder
|
|
||||||
task: 刪除 `folder` 資料夾
|
|
||||||
requirements:
|
|
||||||
- cat-hello
|
|
||||||
condition:
|
|
||||||
files:
|
|
||||||
- path: /home/commander/folder
|
|
||||||
type: folder
|
|
||||||
exists: false
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
刪除 `trash_can` 資料夾。
|
|
||||||
|
|
||||||
- id: END
|
|
||||||
requirements:
|
|
||||||
- rmdir-folder
|
|
||||||
task: 刪除 `trash_can` 資料夾
|
|
||||||
condition:
|
|
||||||
files:
|
|
||||||
- path: /home/commander/trash_can
|
|
||||||
type: folder
|
|
||||||
exists: false
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
恭喜你通過了這個測驗!
|
|
||||||
你可以繼續學習之後的教學內容了。
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
This is `hello.txt`.
|
|
||||||
Whitespace-only changes.
Whitespace-only changes.
@@ -1 +0,0 @@
|
|||||||
aHR0cHM6Ly9yZXVybC5jYy9YUjZZMDA=
|
|
||||||
Whitespace-only changes.
@@ -1,13 +0,0 @@
|
|||||||
echo start
|
|
||||||
ls
|
|
||||||
cat hello.txt
|
|
||||||
cat world.txt
|
|
||||||
echo next
|
|
||||||
cp hello.txt hello2.txt
|
|
||||||
cat hello2.txt
|
|
||||||
cp world.txt world2.txt
|
|
||||||
cat world2.txt
|
|
||||||
cp -r notes notes2
|
|
||||||
ls notes2
|
|
||||||
cp hello.txt world.txt
|
|
||||||
cat world.txt
|
|
||||||
@@ -1,409 +0,0 @@
|
|||||||
title: 複製檔案
|
|
||||||
instruction: |
|
|
||||||
在這個關卡中,你將會學到:
|
|
||||||
1. 如何使用 `cp` 複製檔案
|
|
||||||
|
|
||||||
requirements:
|
|
||||||
- create
|
|
||||||
|
|
||||||
stages:
|
|
||||||
- id: start
|
|
||||||
task: 按下 `開始` 按鈕開始
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^echo start$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
今天我們要學習如何複製檔案。
|
|
||||||
先用 `ls` 指令查看目前資料夾裡有哪些檔案。
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `ls` 來查看目前資料夾裡有哪些檔案
|
|
||||||
|
|
||||||
- id: ls
|
|
||||||
task: 查看目前資料夾裡的檔案
|
|
||||||
requirements:
|
|
||||||
- start
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
command:
|
|
||||||
- ^ls$
|
|
||||||
output:
|
|
||||||
- (^|\s)hello.txt($|\s)
|
|
||||||
- (^|\s)world.txt($|\s)
|
|
||||||
exceptions:
|
|
||||||
- condition:
|
|
||||||
not:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你不在 `/home/commander` 資料夾裡。
|
|
||||||
請使用 `cd /home/commander` 來返回 `/home/commander` 資料夾。
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
可以看到現在資料夾裡有 `hello.txt` 跟 `world.txt`。
|
|
||||||
請使用 `cat` 來查看它們的內容。
|
|
||||||
看完之後,使用 `echo next` 來繼續吧!
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `cat hello.txt` 以及 `cat world.txt` 來查看檔案內容
|
|
||||||
- ➤ 如果終端機卡在某個指令無法輸入,請按下 `Ctrl + C` 來取消該指令
|
|
||||||
|
|
||||||
- id: cat-hello
|
|
||||||
task: 查看 `hello.txt` 的內容
|
|
||||||
requirements:
|
|
||||||
- ls
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
command:
|
|
||||||
- ^cat hello.txt$
|
|
||||||
output:
|
|
||||||
- Hello
|
|
||||||
exceptions:
|
|
||||||
- condition:
|
|
||||||
not:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你不在 `/home/commander` 資料夾裡。
|
|
||||||
請使用 `cd /home/commander` 來返回 `/home/commander` 資料夾
|
|
||||||
- condition:
|
|
||||||
command:
|
|
||||||
- ^cat$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你忘記輸入 `cat` 了嗎?
|
|
||||||
請輸入 `cat hello.txt` 來查看 `hello.txt` 的內容
|
|
||||||
- condition:
|
|
||||||
command:
|
|
||||||
- ^cat hello$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你忘記輸入 `.txt` 了嗎?
|
|
||||||
請輸入 `cat hello.txt` 來查看 `hello.txt` 的內容
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你成功查看到 `hello.txt` 的內容了!
|
|
||||||
|
|
||||||
- id: cat-world
|
|
||||||
task: 查看 `world.txt` 的內容
|
|
||||||
requirements:
|
|
||||||
- ls
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
command:
|
|
||||||
- ^cat world.txt$
|
|
||||||
output:
|
|
||||||
- World
|
|
||||||
exceptions:
|
|
||||||
- condition:
|
|
||||||
not:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你不在 `/home/commander` 資料夾裡。
|
|
||||||
請使用 `cd /home/commander` 來返回 `/home/commander` 資料夾
|
|
||||||
- condition:
|
|
||||||
command:
|
|
||||||
- ^cat$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你忘記輸入 `cat` 了嗎?
|
|
||||||
請輸入 `cat world.txt` 來查看 `world.txt` 的內容
|
|
||||||
- condition:
|
|
||||||
command:
|
|
||||||
- ^cat world$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你忘記輸入 `.txt` 了嗎?
|
|
||||||
請輸入 `cat world.txt` 來查看 `world.txt` 的內容
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你成功查看到 `world.txt` 的內容了!
|
|
||||||
|
|
||||||
- id: cat-next
|
|
||||||
task: 輸入 `echo next` 來繼續
|
|
||||||
requirements:
|
|
||||||
- cat-hello
|
|
||||||
- cat-world
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^echo next$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
接下來我們要來學習如何複製檔案。
|
|
||||||
`cp` 這個指令,是 copy 的縮寫,就是複製的意思。
|
|
||||||
我們可以用 `cp [來源檔案] [目的檔案]` 的方式來複製檔案。
|
|
||||||
請你使用 `cp` 來複製 `hello.txt` 的內容到 `hello2.txt`。
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `cp hello.txt hello2.txt` 來複製 `hello.txt` 到 `hello2.txt`
|
|
||||||
|
|
||||||
- id: cp-hello
|
|
||||||
task: 複製 `hello.txt` 到 `hello2.txt`
|
|
||||||
requirements:
|
|
||||||
- cat-next
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
command:
|
|
||||||
- ^cp hello.txt hello2.txt$
|
|
||||||
files:
|
|
||||||
- path: /home/commander/hello2.txt
|
|
||||||
type: file
|
|
||||||
exceptions:
|
|
||||||
- condition:
|
|
||||||
not:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你不在 `/home/commander` 資料夾裡。
|
|
||||||
請使用 `cd /home/commander` 來返回 `/home/commander` 資料夾。
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你成功複製 `hello.txt` 到 `hello2.txt` 了!
|
|
||||||
我們打開來確認一下 `hello2.txt` 的內容吧!
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `cat hello2.txt` 來查看 `hello2.txt` 的內容
|
|
||||||
|
|
||||||
- id: cat-hello2
|
|
||||||
requirements:
|
|
||||||
- cp-hello
|
|
||||||
task: 查看 `hello2.txt` 的內容
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
command:
|
|
||||||
- ^cat hello2.txt$
|
|
||||||
output:
|
|
||||||
- Hello
|
|
||||||
exceptions:
|
|
||||||
- condition:
|
|
||||||
not:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你不在 `/home/commander` 資料夾裡。
|
|
||||||
請使用 `cd /home/commander` 來返回 `/home/commander` 資料夾。
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你成功查看到 `hello2.txt` 的內容了!
|
|
||||||
正如我們所見,`hello2.txt` 的內容跟 `hello.txt` 一模一樣。
|
|
||||||
那我們再來試試看複製 `world.txt` 到 `world2.txt` 吧!
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `cp world.txt world2.txt` 來複製 `world.txt` 到 `world2.txt`
|
|
||||||
|
|
||||||
- id: cp-world
|
|
||||||
requirements:
|
|
||||||
- cat-hello2
|
|
||||||
task: 複製 `world.txt` 到 `world2.txt`
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
command:
|
|
||||||
- ^cp world.txt world2.txt$
|
|
||||||
files:
|
|
||||||
- path: /home/commander/world2.txt
|
|
||||||
type: file
|
|
||||||
exceptions:
|
|
||||||
- condition:
|
|
||||||
not:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你不在 `/home/commander` 資料夾裡。
|
|
||||||
請使用 `cd /home/commander` 來返回 `/home/commander` 資料夾。
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你成功複製 `world.txt` 到 `world2.txt` 了!
|
|
||||||
我們打開來確認一下 `world2.txt` 的內容吧!
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `cat world2.txt` 來查看 `world2.txt` 的內容
|
|
||||||
|
|
||||||
- id: cat-world2
|
|
||||||
requirements:
|
|
||||||
- cp-world
|
|
||||||
task: 查看 `world2.txt` 的內容
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
command:
|
|
||||||
- ^cat world2.txt$
|
|
||||||
output:
|
|
||||||
- World
|
|
||||||
exceptions:
|
|
||||||
- condition:
|
|
||||||
not:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你不在 `/home/commander` 資料夾裡。
|
|
||||||
請使用 `cd /home/commander` 來返回 `/home/commander` 資料夾。
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你成功查看到 `world2.txt` 的內容了!
|
|
||||||
`world2.txt` 的內容跟 `world.txt` 一模一樣。
|
|
||||||
接下來我們要來學習如何複製資料夾。
|
|
||||||
現在目錄下有一個 `notes` 資料夾,裡面有一個 `note.txt` 的檔案。
|
|
||||||
我們要能整個資料夾包含裡面的檔案一起複製到 `notes2`。
|
|
||||||
跟複製單一檔案不一樣的是,複製資料夾時,我們要加上 `-r` 的選項(recursive)代表將資料夾中的所有檔案都一起複製。
|
|
||||||
在使用指令時,如果想要調整指令的行為,可以在指令後面加上選項。
|
|
||||||
選項的格式通常是 `-` 加上一個字母,例如 `-r`。
|
|
||||||
選項擺放的位置沒有特別的限制,通常會跟在指令的後面,置於檔案名稱的前面。
|
|
||||||
請你使用 `cp -r notes notes2` 來複製 `notes` 資料夾到 `notes2`。
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `cp -r notes notes2` 來複製 `notes` 資料夾到 `notes2`
|
|
||||||
- ➤ 記得要加上 `-r` 的選項來複製所有檔案
|
|
||||||
|
|
||||||
- id: cp-notes
|
|
||||||
requirements:
|
|
||||||
- cat-world2
|
|
||||||
task: 複製 `notes` 資料夾到 `notes2`
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
command:
|
|
||||||
- ^cp
|
|
||||||
files:
|
|
||||||
- path: /home/commander/notes2
|
|
||||||
type: directory
|
|
||||||
- path: /home/commander/notes2/note.txt
|
|
||||||
type: file
|
|
||||||
exceptions:
|
|
||||||
- condition:
|
|
||||||
not:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你不在 `/home/commander` 資料夾裡。
|
|
||||||
請使用 `cd /home/commander` 來返回 `/home/commander` 資料夾。
|
|
||||||
- condition:
|
|
||||||
error:
|
|
||||||
- 'cp: omitting directory'
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你忘記加上 `-r` 的參數了嗎?
|
|
||||||
請輸入 `cp -r notes notes2` 來複製 `notes` 資料夾到 `notes2`
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你成功複製 `notes` 資料夾到 `notes2` 了!
|
|
||||||
我們打開來確認一下 `notes2` 資料夾的內容吧!
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `ls notes2` 來查看 `notes2` 資料夾的內容
|
|
||||||
|
|
||||||
- id: ls-notes2
|
|
||||||
requirements:
|
|
||||||
- cp-notes
|
|
||||||
task: 查看 `notes2` 資料夾的內容
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
command:
|
|
||||||
- ^ls notes2$
|
|
||||||
output:
|
|
||||||
- note.txt
|
|
||||||
exceptions:
|
|
||||||
- condition:
|
|
||||||
not:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你不在 `/home/commander` 資料夾裡。
|
|
||||||
請使用 `cd /home/commander` 來返回 `/home/commander` 資料夾。
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你成功查看到 `notes2` 資料夾的內容了!
|
|
||||||
我們可以看到,整個 `notes` 資料夾包含裡面的檔案,都被複製到 `notes2` 資料夾了。
|
|
||||||
那麼最後我們要來試試看,如果複製一個檔案到已經存在的檔案會發生什麼事。
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `cp hello.txt world.txt` 來複製 `hello.txt` 到 `world.txt`
|
|
||||||
|
|
||||||
- id: cp-overwrite
|
|
||||||
requirements:
|
|
||||||
- ls-notes2
|
|
||||||
task: 複製 `hello.txt` 到 `world.txt`
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
command:
|
|
||||||
- ^cp hello.txt world.txt$
|
|
||||||
exceptions:
|
|
||||||
- condition:
|
|
||||||
not:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你不在 `/home/commander` 資料夾裡。
|
|
||||||
請使用 `cd /home/commander` 來返回 `/home/commander` 資料夾。
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你成功複製 `hello.txt` 到 `world.txt` 了!
|
|
||||||
我們打開來確認一下 `world.txt` 的內容吧!
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `cat world.txt`
|
|
||||||
|
|
||||||
- id: END
|
|
||||||
requirements:
|
|
||||||
- cp-overwrite
|
|
||||||
task: 檢查 `world.txt` 的內容
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
command:
|
|
||||||
- ^cat world.txt$
|
|
||||||
output:
|
|
||||||
- Hello
|
|
||||||
exceptions:
|
|
||||||
- condition:
|
|
||||||
not:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你不在 `/home/commander` 資料夾裡。
|
|
||||||
請使用 `cd /home/commander` 來返回 `/home/commander` 資料夾。
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
`world.txt` 的內容變成 `Hello` 了!
|
|
||||||
這是因為,當我們複製檔案時,如果目的檔案已經存在,那麼目的檔案的內容就會被覆蓋掉。
|
|
||||||
所以在複製檔案的時候,要特別注意目的檔案是否已經存在,否則可能會不小心把資料弄丟。
|
|
||||||
以上就是複製檔案的基本操作!
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Hello
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Note
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
World
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
echo start
|
|
||||||
ls
|
|
||||||
touch hello.txt
|
|
||||||
ls
|
|
||||||
cat hello.txt
|
|
||||||
touch world.txt
|
|
||||||
ls
|
|
||||||
mkdir notes
|
|
||||||
ls
|
|
||||||
cd notes
|
|
||||||
touch note.txt
|
|
||||||
ls
|
|
||||||
@@ -1,228 +0,0 @@
|
|||||||
title: 建立檔案與資料夾
|
|
||||||
instruction: |
|
|
||||||
在這個關卡中,你將會學到:
|
|
||||||
1. 如何使用 `touch` 建立檔案
|
|
||||||
2. 如何使用 `mkdir` 建立資料夾
|
|
||||||
|
|
||||||
requirements:
|
|
||||||
- tab
|
|
||||||
|
|
||||||
stages:
|
|
||||||
- id: start
|
|
||||||
task: 按下 `開始` 按鈕開始
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^echo start$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
這次要學習的指令是 `touch` 指令。
|
|
||||||
請你先用 `ls` 指令查看目前所在資料夾中有哪些檔案。
|
|
||||||
你應該會發現目前所在資料夾中沒有任何檔案。
|
|
||||||
hints:
|
|
||||||
- ➤ 輸入 `ls` 指令來查看目前所在資料夾中有哪些檔案
|
|
||||||
|
|
||||||
- id: ls
|
|
||||||
task: 查看資料夾中的檔案
|
|
||||||
requirements:
|
|
||||||
- start
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^ls$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你應該可以看到終端機上沒有任何回應,這表示目前所在資料夾中沒有任何檔案。
|
|
||||||
而我們可以使用 `touch [檔案名稱]` 指令來建立一個檔案。
|
|
||||||
接下來,請你試著在終端機上建立一個檔案,檔名為 `hello.txt`。
|
|
||||||
hints:
|
|
||||||
- ➤ 輸入 `touch hello.txt` 來建立一個檔案
|
|
||||||
|
|
||||||
- id: touch
|
|
||||||
task: 使用 `touch` 指令來建立一個檔案
|
|
||||||
requirements:
|
|
||||||
- ls
|
|
||||||
condition:
|
|
||||||
files:
|
|
||||||
- path: /home/commander/hello.txt
|
|
||||||
type: file
|
|
||||||
exceptions:
|
|
||||||
- condition:
|
|
||||||
files:
|
|
||||||
- path: /home/commander/hello
|
|
||||||
type: file
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你似乎忘記輸入副檔名 `txt` 了。
|
|
||||||
請再試一次,並確保檔名為 `hello.txt`。
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你應該可以看到終端機上沒有任何回應。
|
|
||||||
然而實際上 `touch` 指令已經成功建立了一個檔案。
|
|
||||||
請你用之前學過的 `ls` 指令來查看目前所在資料夾中有哪些檔案。
|
|
||||||
hints:
|
|
||||||
- ➤ 輸入 `ls` 指令來查看目前所在資料夾中有哪些檔案
|
|
||||||
|
|
||||||
- id: ls-touch
|
|
||||||
task: 使用 `ls` 指令來查看新建立的檔案
|
|
||||||
requirements:
|
|
||||||
- touch
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^ls$
|
|
||||||
output:
|
|
||||||
- hello.txt
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你應該可以看到終端機上顯示了 `hello.txt` 這個檔案。
|
|
||||||
我們來看看 `hello.txt` 檔案的內容吧!
|
|
||||||
我們可以使用之前學過的 `cat` 指令來查看檔案內容。
|
|
||||||
hints:
|
|
||||||
- ➤ 輸入 `cat hello.txt` 來查看 `hello.txt` 檔案的內容
|
|
||||||
|
|
||||||
- id: cat
|
|
||||||
task: 使用 `cat` 指令來查看 `hello.txt` 檔案的內容
|
|
||||||
requirements:
|
|
||||||
- ls-touch
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^cat hello.txt$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
咦?竟然什麼都沒有!
|
|
||||||
沒錯,使用 `touch` 指令建立的檔案是空的。
|
|
||||||
我們在未來會學習如何編輯檔案的內容。
|
|
||||||
再來試試建立其他的檔案吧!
|
|
||||||
請你試著在終端機上建立另一個檔案,檔名為 `world.txt`。
|
|
||||||
hints:
|
|
||||||
- ➤ 輸入 `touch world.txt` 來建立一個檔案
|
|
||||||
|
|
||||||
- id: touch-2
|
|
||||||
task: 使用 `touch` 指令來建立另一個檔案 `world.txt`
|
|
||||||
requirements:
|
|
||||||
- ls-touch
|
|
||||||
condition:
|
|
||||||
files:
|
|
||||||
- path: /home/commander/world.txt
|
|
||||||
type: file
|
|
||||||
exceptions:
|
|
||||||
- condition:
|
|
||||||
files:
|
|
||||||
- path: /home/commander/world
|
|
||||||
type: file
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你似乎忘記輸入副檔名 `txt` 了。
|
|
||||||
請再試一次,並確保檔名為 `world.txt`。
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
非常好,你已經成功建立了第二個檔案。
|
|
||||||
接下來,請你用之前學過的 `ls` 指令來查看剛剛建立的兩個檔案。
|
|
||||||
hints:
|
|
||||||
- ➤ 輸入 `ls` 指令來查看目前所在資料夾中有哪些檔案
|
|
||||||
|
|
||||||
- id: ls-2
|
|
||||||
task: 使用 `ls` 指令來查看剛剛建立的兩個檔案
|
|
||||||
requirements:
|
|
||||||
- touch-2
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^ls$
|
|
||||||
output:
|
|
||||||
- (^|\s)hello.txt($|\s)
|
|
||||||
- (^|\s)world.txt($|\s)
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你應該可以看到終端機上顯示了 `hello.txt` 和 `world.txt` 這兩個檔案。
|
|
||||||
接下來,我們來試試看建立資料夾吧!
|
|
||||||
`mkdir` 指令可以建立一個空的資料夾。
|
|
||||||
使用方式是 `mkdir [資料夾名稱]`。
|
|
||||||
請你試著在終端機上建立一個資料夾,名稱為 `notes`。
|
|
||||||
hints:
|
|
||||||
- ➤ 輸入 `mkdir notes` 來建立一個資料夾
|
|
||||||
|
|
||||||
- id: mkdir
|
|
||||||
task: 使用 `mkdir` 指令建立 `notes` 資料夾
|
|
||||||
requirements:
|
|
||||||
- ls-2
|
|
||||||
condition:
|
|
||||||
files:
|
|
||||||
- path: /home/commander/notes
|
|
||||||
type: folder
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
非常好,你已經成功建立了 `notes` 資料夾。
|
|
||||||
接下來,請你用 `ls` 指令來查看剛剛建立的資料夾。
|
|
||||||
|
|
||||||
- id: ls-mkdir
|
|
||||||
task: 使用 `ls` 查看剛建立的資料夾
|
|
||||||
requirements:
|
|
||||||
- mkdir
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^ls$
|
|
||||||
output:
|
|
||||||
- notes
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你應該可以看到終端機上顯示了 `notes` 這個資料夾。
|
|
||||||
接下來,我們要在 `notes` 資料夾中建立一個 `note.txt` 的檔案。
|
|
||||||
首先,我們要先進入 `notes` 資料夾。
|
|
||||||
還記得之前的 `cd` 指令嗎?請你試著使用 `cd` 指令來進入 `notes` 資料夾。
|
|
||||||
hints:
|
|
||||||
- ➤ 輸入 `cd notes` 來進入 `notes` 資料夾
|
|
||||||
- ➤ 可以在輸入到 `cd n` 時按下 `Tab` 鍵來自動補齊資料夾名稱
|
|
||||||
|
|
||||||
- id: cd-notes
|
|
||||||
task: 移動到 `notes` 資料夾
|
|
||||||
requirements:
|
|
||||||
- ls-mkdir
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander/notes$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你已經成功進入了 `notes` 資料夾。
|
|
||||||
接下來,請你試著在 `notes` 資料夾裡建立一個檔案,檔名為 `note.txt`。
|
|
||||||
hints:
|
|
||||||
- ➤ 輸入 `touch note.txt` 來建立一個檔案
|
|
||||||
|
|
||||||
- id: touch-note
|
|
||||||
task: 在 `notes` 中建立 `note.txt` 檔案
|
|
||||||
requirements:
|
|
||||||
- cd-notes
|
|
||||||
condition:
|
|
||||||
files:
|
|
||||||
- path: /home/commander/notes/note.txt
|
|
||||||
type: file
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
非常好,你已經成功建立了 `note.txt` 檔案。
|
|
||||||
跟之前一樣,你可以使用 `ls` 指令來查看目前所在資料夾中有哪些檔案。
|
|
||||||
|
|
||||||
- id: END
|
|
||||||
task: 使用 `ls` 指令來查看目前所在資料夾中有哪些檔案
|
|
||||||
requirements:
|
|
||||||
- touch-note
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^ls
|
|
||||||
output:
|
|
||||||
- note.txt
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你應該可以看到終端機上顯示了 `note.txt` 這個檔案。
|
|
||||||
恭喜你完成了這個關卡!
|
|
||||||
在下一個關卡中,你將學習如何複製檔案與資料夾。
|
|
||||||
+86
-88
@@ -1,99 +1,97 @@
|
|||||||
exceptions:
|
- id: exception-fullwidth-letters
|
||||||
- condition:
|
condition:
|
||||||
command:
|
command: '[\uff21-\uff3a\uff41-\uff5a]'
|
||||||
- '[\uff21-\uff3a\uff41-\uff5a]'
|
response:
|
||||||
responses:
|
type: narrative
|
||||||
- type: narrative
|
content: |
|
||||||
content: |
|
!! 請使用半形英文字母 !!
|
||||||
!! 請使用半形英文字母 !!
|
|
||||||
|
|
||||||
- condition:
|
- id: exception-fullwidth-space
|
||||||
command:
|
condition:
|
||||||
- ' '
|
command: ' '
|
||||||
responses:
|
response:
|
||||||
- type: narrative
|
type: narrative
|
||||||
content: |
|
content: |
|
||||||
!! 請使用半形空格 !!
|
!! 請使用半形空格 !!
|
||||||
|
|
||||||
- condition:
|
- id: exception-echo-no-space
|
||||||
command:
|
condition:
|
||||||
- ^echo\S
|
command: ^echo\S
|
||||||
responses:
|
response:
|
||||||
- type: narrative
|
type: narrative
|
||||||
content: |
|
content: |
|
||||||
!! `echo` 後面要加空格 !!
|
!! `echo` 後面要加空格 !!
|
||||||
|
|
||||||
- condition:
|
- id: exception-cd-no-space
|
||||||
command:
|
condition:
|
||||||
- ^cd\S
|
command: ^cd\S
|
||||||
responses:
|
response:
|
||||||
- type: narrative
|
type: narrative
|
||||||
content: |
|
content: |
|
||||||
!! `cd` 後面要加空格 !!
|
!! `cd` 後面要加空格 !!
|
||||||
|
|
||||||
- condition:
|
- id: exception-ls-no-space
|
||||||
command:
|
condition:
|
||||||
- ^ls\S
|
command: ^ls\S
|
||||||
responses:
|
response:
|
||||||
- type: narrative
|
type: narrative
|
||||||
content: |
|
content: |
|
||||||
!! `ls` 後面要加空格 !!
|
!! `ls` 後面要加空格 !!
|
||||||
|
|
||||||
- condition:
|
- id: exception-cat-no-space
|
||||||
command:
|
condition:
|
||||||
- ^cat\S
|
command: ^cat\S
|
||||||
responses:
|
response:
|
||||||
- type: narrative
|
type: narrative
|
||||||
content: |
|
content: |
|
||||||
!! `cat` 後面要加空格 !!
|
!! `cat` 後面要加空格 !!
|
||||||
|
|
||||||
- condition:
|
- id: exception-touch-no-space
|
||||||
command:
|
condition:
|
||||||
- ^touch\S
|
command: ^touch\S
|
||||||
responses:
|
response:
|
||||||
- type: narrative
|
type: narrative
|
||||||
content: |
|
content: |
|
||||||
!! `touch` 後面要加空格 !!
|
!! `touch` 後面要加空格 !!
|
||||||
|
|
||||||
- condition:
|
- id: exception-rm-no-space
|
||||||
not:
|
condition:
|
||||||
command:
|
not:
|
||||||
- ^rmdir
|
command: ^rmdir
|
||||||
command:
|
command: ^rm\S
|
||||||
- ^rm\S
|
response:
|
||||||
responses:
|
type: narrative
|
||||||
- type: narrative
|
content: |
|
||||||
content: |
|
!! `rm` 後面要加空格 !!
|
||||||
!! `rm` 後面要加空格 !!
|
|
||||||
|
|
||||||
- condition:
|
- id: exception-mv-no-space
|
||||||
command:
|
condition:
|
||||||
- ^mv\S
|
command: ^mv\S
|
||||||
responses:
|
response:
|
||||||
- type: narrative
|
type: narrative
|
||||||
content: |
|
content: |
|
||||||
!! `mv` 後面要加空格 !!
|
!! `mv` 後面要加空格 !!
|
||||||
|
|
||||||
- condition:
|
- id: exception-mkdir-no-space
|
||||||
command:
|
condition:
|
||||||
- ^mkdir\S
|
command: ^mkdir\S
|
||||||
responses:
|
response:
|
||||||
- type: narrative
|
type: narrative
|
||||||
content: |
|
content: |
|
||||||
!! `mkdir` 後面要加空格 !!
|
!! `mkdir` 後面要加空格 !!
|
||||||
|
|
||||||
- condition:
|
- id: exception-rmdir-no-space
|
||||||
command:
|
condition:
|
||||||
- ^rmdir\S
|
command: ^rmdir\S
|
||||||
responses:
|
response:
|
||||||
- type: narrative
|
type: narrative
|
||||||
content: |
|
content: |
|
||||||
!! `rmdir` 後面要加空格 !!
|
!! `rmdir` 後面要加空格 !!
|
||||||
|
|
||||||
- condition:
|
- id: exception-cp-no-space
|
||||||
command:
|
condition:
|
||||||
- ^cp\S
|
command: ^cp\S
|
||||||
responses:
|
response:
|
||||||
- type: narrative
|
type: narrative
|
||||||
content: |
|
content: |
|
||||||
!! `cp` 後面要加空格 !!
|
!! `cp` 後面要加空格 !!
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
# 執行檔案
|
|
||||||
|
|
||||||
## Outline
|
|
||||||
|
|
||||||
1. 什麼是可執行檔
|
|
||||||
2. 使用 `which` 指令查看執行檔的位置
|
|
||||||
3. 如何執行可執行檔
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
在我們先前的課程裡,已經使用了許多不同的指令,像是 `ls`, `cat`, `cp`, `mv`, `rm` 等。
|
|
||||||
這些指令,其實都是一個**可執行檔**,在你打指令的時候被作業系統執行。
|
|
||||||
所以大部分的指令,我們其實都能找到它在檔案系統裡的原始位置。
|
|
||||||
要查看一個指令它的檔案位置,可以使用 `which` 指令。
|
|
||||||
輸入 `which [指令名稱]` 就能夠查看指令的檔案位置了。
|
|
||||||
|
|
||||||
> 使用 `which ls` 來查看 `ls` 的路徑
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
你看到終端機顯示出 `/bin/ls` 這個路徑,它其實就是 `ls` 指令它檔案存在的位置。
|
|
||||||
`/bin` 是一個系統資料夾,裡面會儲存許多系統內建的程式,像是 `cat`, `cp` 等指令也都被存在這裡。
|
|
||||||
|
|
||||||
> 使用 `which cat` 查看 `cat` 的路徑
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
我們要執行一個可執行檔時,只要直接輸入它的路徑就可以執行它。
|
|
||||||
我們來試看看使用執行檔的路徑來執行指令。
|
|
||||||
|
|
||||||
> 輸入 `/bin/ls` 來查看當前的資料夾
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
你會看到它列出了我們資料夾內的檔案,你可以自己比較看看,跟單純打 `ls` 的結果會一模一樣。
|
|
||||||
它一樣可以使用參數。我們來用 `cat` 試試看。
|
|
||||||
|
|
||||||
> 輸入 `/bin/cat hello.txt` 來顯示 `hello.txt` 的內容
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
你會看到了 `hello.txt` 的內容,效果跟單純用 `cat` 一模一樣。
|
|
||||||
事實上,這是必然的,因為 `cat` 的檔案位置就是 `/bin/cat`,我們執行了同一個程式,當然會有同樣的結果。
|
|
||||||
其實指令,就是由 **一個執行檔** + **參數** 所構成。
|
|
||||||
我們之所以不用打 `/bin/ls`,而是只要打 `ls` 就能執行,是因為有 `PATH` 這個環境變數,幫我們預設好了終端機要去哪些*資料夾*找檔案。
|
|
||||||
但因為這超出了本次課程的範圍,關於 `PATH` 以及環境變數的議題,我們會在未來的課程再討論。
|
|
||||||
|
|
||||||
我們也可以執行不是內建的指令,像是現在資料夾中的 `dog` 檔案。
|
|
||||||
|
|
||||||
> 使用 `cat dog` 來查看 `dog` 的內容
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
你會看到這是一個用 `echo` 和 `cat` 所組成的簡單程式。
|
|
||||||
詳細的內容不用了解沒關係,功能是和 `cat` 一樣,用來印出檔案內容的程式。
|
|
||||||
但是這支程式,在沒有給要印出的檔案時,不會停留在輸入狀態。
|
|
||||||
|
|
||||||
當我們要執行一個檔案時,就是用指定路徑的方式。
|
|
||||||
那因為 `dog` 位在家目錄底下,所以牠的絕對路徑就是 `/home/commander/dog`,我們也可以用簡寫 `~/dog`。
|
|
||||||
|
|
||||||
> 輸入 `~/dog` 來執行 `dog` 檔案
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
你看到了終端機輸出這隻傲嬌的狗。
|
|
||||||
除了使用絕對路徑,很多時候,我們會處在跟程式相同的資料夾,這時使用相對路徑會更為便利。
|
|
||||||
但跟平常列出檔案時不一樣,我們為了區別它不是內建的指令,需要加上 `./` 在檔名前面。
|
|
||||||
`.` 在路徑中,代表的就是當前的資料夾。
|
|
||||||
那我們再試試看使用相對路徑,並讓牠接收一個檔案作為參數。
|
|
||||||
|
|
||||||
> 輸入 `./dog hello.txt` 來印出 `hello.txt` 的內容
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
你看到這隻狗在講了幾句話後也印出了 `hello.txt` 的內容了。
|
|
||||||
這個範例,其實就是一個簡單的自訂指令。
|
|
||||||
我們可以把一大串指令,寫在一個檔案裡讓它自動執行,幫我們處理一些複雜或是重複性的工作。
|
|
||||||
我們在使用別人寫的程式時,也常會用到這個技巧。
|
|
||||||
|
|
||||||
最後留一個小挑戰給你,請試著移動到 `commands` 資料夾後,再使用相對路徑執行裡面的 `lucky` 檔案。
|
|
||||||
@@ -1,187 +0,0 @@
|
|||||||
title: 執行檔案
|
|
||||||
instruction: |
|
|
||||||
在這個關卡中,你將會學到:
|
|
||||||
1. 什麼是可執行檔
|
|
||||||
2. 使用 `which` 指令查看執行檔的位置
|
|
||||||
3. 如何執行可執行檔
|
|
||||||
|
|
||||||
requirements:
|
|
||||||
- remove
|
|
||||||
|
|
||||||
stages:
|
|
||||||
- id: start
|
|
||||||
task: 按下開始按鈕
|
|
||||||
type: start
|
|
||||||
condition:
|
|
||||||
command: ^echo start$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
在我們先前的課程裡,已經使用了許多不同的指令,像是 `ls`, `cat`, `cp`, `mv`, `rm` 等。
|
|
||||||
這些指令,其實都是一個**可執行檔**,在你打指令的時候被作業系統執行。
|
|
||||||
所以大部分的指令,我們其實都能找到它在檔案系統裡的原始位置。
|
|
||||||
要查看一個指令它的檔案位置,可以使用 `which` 指令。
|
|
||||||
輸入 `which [指令名稱]` 就能夠查看指令的檔案位置了。
|
|
||||||
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `which ls` 來查看 `ls` 的路徑
|
|
||||||
|
|
||||||
- id: which-ls
|
|
||||||
requirements:
|
|
||||||
- start
|
|
||||||
task: 使用 `which` 指令查看 `ls` 的路徑
|
|
||||||
condition:
|
|
||||||
command: ^which ls$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
你看到終端機顯示出 `/bin/ls` 這個路徑,它其實就是 `ls` 指令它檔案存在的位置。
|
|
||||||
`/bin` 是一個系統資料夾,裡面會儲存許多系統內建的程式,像是 `cat`, `cp` 等指令也都被存在這裡。
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `which cat` 查看 `cat` 的路徑
|
|
||||||
|
|
||||||
- id: which-cat
|
|
||||||
requirements:
|
|
||||||
- which-ls
|
|
||||||
task: 使用 `which` 指令查看 `cat` 的路徑
|
|
||||||
condition:
|
|
||||||
command: ^which cat$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
你看到了 `/bin/cat` 這個路徑,它就是 `cat` 指令它檔案存在的位置。
|
|
||||||
我們要執行一個可執行檔時,只要 **直接輸入它的路徑** 就可以執行它。
|
|
||||||
我們來試看看使用執行檔的路徑來執行指令。
|
|
||||||
|
|
||||||
在此之前,我們先試試看一般執行 `ls` 指令的結果。
|
|
||||||
hints:
|
|
||||||
- ➤ 輸入 `ls` 來查看當前的資料夾
|
|
||||||
|
|
||||||
- id: ls
|
|
||||||
requirements:
|
|
||||||
- which-cat
|
|
||||||
task: 使用 `ls` 指令查看當前的資料夾
|
|
||||||
condition:
|
|
||||||
command: ^ls$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
你看到了當前的資料夾內有 `commands` 資料夾和 `dog`, `hello.txt` 這兩個檔案。
|
|
||||||
我們再來試看看用 `ls` 的路徑來執行它。
|
|
||||||
hints:
|
|
||||||
- ➤ 輸入 `/bin/ls` 來查看當前的資料夾
|
|
||||||
|
|
||||||
- id: bin-ls
|
|
||||||
requirements:
|
|
||||||
- ls
|
|
||||||
task: 使用執行檔的絕對路徑來執行 `ls`
|
|
||||||
condition:
|
|
||||||
command: ^/bin/ls$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
你會看到它列出了我們資料夾內的檔案,你會發現它的輸出跟單純打 `ls` 的結果一模一樣。
|
|
||||||
它一樣可以使用參數。我們來用 `cat` 試試看。
|
|
||||||
|
|
||||||
hints:
|
|
||||||
- ➤ 輸入 `/bin/cat hello.txt` 來顯示 `hello.txt` 的內容
|
|
||||||
|
|
||||||
- id: bin-cat
|
|
||||||
requirements:
|
|
||||||
- bin-ls
|
|
||||||
task: 使用執行檔的絕對路徑來執行 `cat`
|
|
||||||
condition:
|
|
||||||
command: ^/bin/cat hello.txt$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
你看到了 `hello.txt` 的內容,跟單純使用 `cat` 一模一樣。
|
|
||||||
事實上,這是必然的,因為 `cat` 的檔案位置就是 `/bin/cat`,我們執行了同一個程式,當然會有同樣的結果。
|
|
||||||
|
|
||||||
因為一個指令,就是由 `執行檔 參數1 參數2 ...` 所構成。
|
|
||||||
`執行檔` 就是我們要執行的程式,像是 `ls`, `cat`, `which` 等。
|
|
||||||
`參數` 則是我們要提供給程式的資訊,像是接在 `cat` 後面的 `hello.txt`。
|
|
||||||
|
|
||||||
我們之所以不用打 `/bin/ls`,而是只要打 `ls` 就能執行,是因為有 `PATH` 這個環境變數,幫我們預設好了終端機要去哪些*資料夾*找檔案。
|
|
||||||
但因為這超出了本次課程的範圍,關於 `PATH` 以及環境變數的議題,我們會在未來的課程再討論。
|
|
||||||
|
|
||||||
接著我們來試著執行不是內建的指令。
|
|
||||||
現在資料夾中的 `dog` 檔案,是一個在這門課程所提供的自訂指令。
|
|
||||||
我們先來看看它是怎麼寫的。
|
|
||||||
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `cat dog` 來查看 `dog` 的內容
|
|
||||||
|
|
||||||
- id: cat-dog
|
|
||||||
requirements:
|
|
||||||
- bin-cat
|
|
||||||
task: 使用 `cat` 指令查看 `dog` 的內容
|
|
||||||
condition:
|
|
||||||
command: ^cat dog$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
你看到了這是一個用 `echo` 和 `sleep` 所組成的簡單程式。
|
|
||||||
詳細的內容不用了解沒關係,只是輸出一些文字而已。
|
|
||||||
|
|
||||||
當我們要執行一個檔案時,就是用指定路徑的方式。
|
|
||||||
也就是說 `執行檔` 就是 `dog` 的路徑,`參數` 在這個範例中不會使用到。
|
|
||||||
那因為 `dog` 位在家目錄底下,所以牠的絕對路徑就是 `/home/commander/dog`。
|
|
||||||
那在 `執行檔 參數` 這個格式下,我們要打的指令就會是
|
|
||||||
```
|
|
||||||
/home/commander/dog
|
|
||||||
```
|
|
||||||
|
|
||||||
hints:
|
|
||||||
- ➤ 輸入 `/home/commander/dog` 來執行 `dog` 檔案
|
|
||||||
|
|
||||||
- id: dog-abs
|
|
||||||
requirements:
|
|
||||||
- cat-dog
|
|
||||||
task: 使用絕對路徑來執行 `dog`
|
|
||||||
condition:
|
|
||||||
or:
|
|
||||||
- command: ^/home/commander/dog$
|
|
||||||
- command: ^~/dog$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
你看到了終端機輸出這隻傲嬌的狗。
|
|
||||||
除了使用絕對路徑,很多時候,我們會處在跟程式相同的資料夾,這時使用相對路徑會更為便利。
|
|
||||||
但跟平常列出檔案時不一樣,我們為了區別它不是內建的指令,需要加上 `./` 在檔名前面。
|
|
||||||
`.` 在路徑中,代表的就是當前的資料夾。
|
|
||||||
那我們再試試看使用相對路徑來執行 `dog` 檔案。
|
|
||||||
|
|
||||||
hints:
|
|
||||||
- ➤ 輸入 `./dog` 來執行 `dog` 檔案
|
|
||||||
|
|
||||||
- id: dog-rel
|
|
||||||
requirements:
|
|
||||||
- dog-abs
|
|
||||||
task: 使用相對路徑來執行 `dog`,並加上參數
|
|
||||||
condition:
|
|
||||||
pwd: /home/commander
|
|
||||||
command: ^./dog$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
你看到這隻狗執行了跟剛才一樣的結果。
|
|
||||||
這個範例,其實就是一個簡單的自訂指令。
|
|
||||||
我們可以把一大串指令,寫在一個檔案裡讓它自動執行,幫我們處理一些複雜或是重複性的工作。
|
|
||||||
我們在使用別人寫的程式時,也常會用到這個技巧。
|
|
||||||
|
|
||||||
最後留一個小挑戰給你,請試著先進入 `commands` 資料夾後,再使用相對路徑執行裡面的 `lucky` 檔案。
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `cd` 來進入資料夾
|
|
||||||
- ➤ 使用 `ls` 查看檔案
|
|
||||||
- ➤ 輸入 `./檔案名稱` 來執行檔案
|
|
||||||
|
|
||||||
- id: END
|
|
||||||
requirements:
|
|
||||||
- dog-rel
|
|
||||||
task: 使用相對路徑來執行 `lucky`
|
|
||||||
condition:
|
|
||||||
pwd: /home/commander/commands
|
|
||||||
command: ^./lucky$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
你看到了終端機輸出 `You are lucky!` 這個字串。
|
|
||||||
恭喜你,你已經完成了這個課程!
|
|
||||||
exceptions:
|
|
||||||
- condition:
|
|
||||||
command: ^echo help$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
你需要先移動到 `commands` 資料夾,再使用相對路徑執行 `lucky` 檔案。
|
|
||||||
請先輸入 `cd commands` 來移動到 `commands` 資料夾,再使用相對路徑 `./lucky` 來執行 `lucky` 檔案。
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
echo "I am SO LUCKY!!!"
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
echo "你執行了 dog 檔案"
|
|
||||||
echo "汪汪汪"
|
|
||||||
sleep 1
|
|
||||||
echo "你知道嗎?"
|
|
||||||
sleep 1
|
|
||||||
echo "我比貓還厲害"
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Hello! This is a file hello.txt
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
echo start
|
|
||||||
ls
|
|
||||||
cd folder1
|
|
||||||
ls
|
|
||||||
cd ..
|
|
||||||
cd folder2
|
|
||||||
ls
|
|
||||||
cd ..
|
|
||||||
@@ -1,175 +0,0 @@
|
|||||||
title: 探索檔案
|
|
||||||
instruction: |
|
|
||||||
在這個關卡中,你將會學到:
|
|
||||||
1. 如何使用 `ls` 查看資料夾的內容
|
|
||||||
2. 如何使用 `cd` 在資料夾之間移動
|
|
||||||
|
|
||||||
requirements:
|
|
||||||
- get-started
|
|
||||||
|
|
||||||
stages:
|
|
||||||
- id: start
|
|
||||||
task: 按下 `開始` 按鈕開始教程
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^echo start$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
在終端機中,並沒有圖形介面可以讓你查看資料夾的內容。
|
|
||||||
但是你可以透過 `ls` 指令來查看資料夾的內容。
|
|
||||||
`ls` 指令(第一個字是小寫L)是 list 的簡寫,也就是列出檔案的意思。
|
|
||||||
在輸入 `ls` 指令後,終端機會輸出資料夾中包含的檔案和資料夾。
|
|
||||||
現在,請你輸入 `ls` 指令,查看資料夾中有什麼東西。
|
|
||||||
hints:
|
|
||||||
- ➤ 輸入 `ls` 並查看資料夾的內容
|
|
||||||
|
|
||||||
- id: ls
|
|
||||||
task: 查看資料夾中的檔案
|
|
||||||
requirements:
|
|
||||||
- start
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^ls$
|
|
||||||
exceptions:
|
|
||||||
- condition:
|
|
||||||
not:
|
|
||||||
pwd:
|
|
||||||
- /home/commander
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
請先回到 `/home/commander/` 資料夾
|
|
||||||
輸入 `cd ~` 來返回 `/home/commander/` 資料夾
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
你應該能看到資料夾中的內容了。
|
|
||||||
在畫面右邊的檔案樹也有了改變。
|
|
||||||
可以看出,在這個資料夾中包含了兩個資料夾,分別是 `folder1` 和 `folder2` 。
|
|
||||||
- content: |
|
|
||||||
接下來,我要教你如何進入資料夾。
|
|
||||||
`cd` 指令是 change directory 的縮寫,也就是改變路徑的意思。
|
|
||||||
你可以通過輸入 `cd [資料夾名稱]` 來進入資料夾。
|
|
||||||
例如你要進入 `folder1` 資料夾,你可以輸入 `cd folder1` 。
|
|
||||||
現在,請你進入 `folder1` 資料夾。
|
|
||||||
hints:
|
|
||||||
- ➤ 輸入 `cd folder1` 來進入 `folder1` 資料夾
|
|
||||||
|
|
||||||
- id: cd-folder1
|
|
||||||
task: 進入 `folder1` 資料夾
|
|
||||||
requirements:
|
|
||||||
- ls
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander/folder1$
|
|
||||||
exceptions:
|
|
||||||
- condition:
|
|
||||||
command:
|
|
||||||
- folderl
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
`folder1` 的最後是數字 `1` 不是字母 `l`。
|
|
||||||
- condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: 輸入 `cd folder1` 來進入 `folder1` 資料夾
|
|
||||||
- condition:
|
|
||||||
not:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
請先回到 `/home/commander/` 資料夾
|
|
||||||
輸入 `cd ~` 來返回 `/home/commander/` 資料夾
|
|
||||||
再輸入 `cd folder1` 來進入 `folder1` 資料夾
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
你成功的進入 `folder1` 資料夾了。
|
|
||||||
你可以發現指令提示符從原本的
|
|
||||||
`commander:~ $` 變成了
|
|
||||||
`commander:~/folder1 $` 。
|
|
||||||
這表示你現在正位在 `folder1` 資料夾中。
|
|
||||||
現在,請查看 `folder1` 資料夾的內容。
|
|
||||||
hints:
|
|
||||||
- ➤ 輸入 `ls` 查看資料夾的內容
|
|
||||||
|
|
||||||
- id: ls-folder1
|
|
||||||
task: 查看 `folder1` 資料夾的內容
|
|
||||||
requirements:
|
|
||||||
- cd-folder1
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander/folder1$
|
|
||||||
command:
|
|
||||||
- ^ls$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
你現在可以看到 `folder1` 資料夾中有兩個檔案,分別是 `file1.txt` 跟 `file2.txt`。
|
|
||||||
現在我們可以離開 `folder1` 回到原本的資料夾了。
|
|
||||||
你可以使用 `cd ..` 回到上一層資料夾。
|
|
||||||
這裡的 `..` 代表的是上一層的資料夾。
|
|
||||||
hints:
|
|
||||||
- ➤ 輸入 `cd ..` 回到上一層資料夾
|
|
||||||
|
|
||||||
- id: cd-..
|
|
||||||
task: 回到上一層資料夾
|
|
||||||
requirements:
|
|
||||||
- ls-folder1
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
你成功的回到上一層資料夾了。
|
|
||||||
現在,請你剛才所學的指令,進入 `folder2` 資料夾,檢查 `folder2` 資料夾的內容,最後回到這個資料夾。
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `cd` 指令進出資料夾
|
|
||||||
- ➤ 使用 `ls` 指令查看資料夾的內容
|
|
||||||
|
|
||||||
- id: cd-folder2
|
|
||||||
task: 進入 `folder2` 資料夾
|
|
||||||
requirements:
|
|
||||||
- cd-..
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander/folder2$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
你成功的進入 `folder2` 資料夾了。
|
|
||||||
現在,請你查看 `folder2` 資料夾的內容。
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `ls` 查看資料夾的內容
|
|
||||||
|
|
||||||
- id: ls-folder2
|
|
||||||
task: 查看 `folder2` 資料夾的內容
|
|
||||||
requirements:
|
|
||||||
- cd-folder2
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander/folder2$
|
|
||||||
command:
|
|
||||||
- ^ls$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
你看到了 `folder2` 資料夾中的檔案,分別是 `file3.txt` 跟 `file4.txt`。
|
|
||||||
現在你可以離開 `folder2` 回到原本的資料夾了。
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `cd ..` 回到上一層資料夾
|
|
||||||
|
|
||||||
- id: END
|
|
||||||
task: 回到原本的資料夾
|
|
||||||
requirements:
|
|
||||||
- ls-folder2
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
你成功的回到原本的資料夾了。
|
|
||||||
恭喜你學會了 `ls` 和 `cd` 指令的用法。
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `cd` 指令進出資料夾
|
|
||||||
- ➤ 使用 `ls` 指令查看資料夾的內容
|
|
||||||
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
@@ -1,4 +0,0 @@
|
|||||||
echo start
|
|
||||||
echo hello
|
|
||||||
echo next
|
|
||||||
echo finish
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
title: 終端機的基本知識
|
|
||||||
instruction: |
|
|
||||||
歡迎來到 Linux Odyssey!
|
|
||||||
在這個關卡中,你將會學到:
|
|
||||||
1. 如何使用這個網站
|
|
||||||
2. 如何使用終端機
|
|
||||||
|
|
||||||
stages:
|
|
||||||
- id: start
|
|
||||||
task: 按下 `開始` 按鈕開始
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^echo start$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
在畫面的中下方的區塊是終端機,你可以在終端機裡輸入指令來操作電腦。
|
|
||||||
在終端機中,你所看到的 `commander:~ $` 叫做命令提示字串。
|
|
||||||
命令提示字串最後的錢字號 `$` 叫命令提示符,你必須將指令輸入在它的後方。
|
|
||||||
當你輸入完指令並按下 Enter 之後,終端機會執行你輸入的指令,並且會從終端機的下一行開始輸出指令的執行結果。
|
|
||||||
直到你看到新的 `commander:~ $` 出現時,代表你可以輸入下一個指令了。
|
|
||||||
現在請你輸入 `echo hello` 來看看會發生什麼事。
|
|
||||||
hints:
|
|
||||||
- ➤ 在 `commander:~ $` 後面輸入 `echo hello` 並按下 Enter
|
|
||||||
|
|
||||||
- id: echo-hello
|
|
||||||
task: 在終端機輸入 `echo hello`
|
|
||||||
requirements:
|
|
||||||
- start
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^echo hello$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
你應該可以看到終端機上顯示了以下內容:
|
|
||||||
```
|
|
||||||
commander:~ $ echo hello
|
|
||||||
hello
|
|
||||||
```
|
|
||||||
第一行的 `commander:~ $` 是命令提示字串,接在後方的 `echo hello` 是你輸入的指令。
|
|
||||||
第二行的 `hello` 則是終端機的輸出結果。
|
|
||||||
你現在知道了如何在終端機上輸入指令,以及查看終端機輸出的位置。
|
|
||||||
現在請輸入 `echo next` 來繼續下一個步驟。
|
|
||||||
hints:
|
|
||||||
- ➤ 輸入 `echo next` 繼續下一個步驟
|
|
||||||
|
|
||||||
- id: echo-next
|
|
||||||
task: 在終端機輸入 `echo next`
|
|
||||||
requirements:
|
|
||||||
- echo-hello
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^echo next$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
`Ctrl + C` 在終端機中是一個很重要的快捷鍵,它可以用來中斷正在執行的指令,並產生一個新的命令提示字串,讓你可以繼續輸入指令。
|
|
||||||
如果指令執行很久沒有反應,或你因為其他原因想要中斷指令,你可以按下 `Ctrl + C` 來中斷指令。
|
|
||||||
又或者你輸入了錯誤的指令,卻懶得慢慢刪除,你也可以按下 `Ctrl + C` 來產生新的命令提示字串重新輸入指令。
|
|
||||||
如果你已經理解了剛才的說明,請輸入 `echo finish` 來結束這一關。
|
|
||||||
hints:
|
|
||||||
- ➤ 按下 `Ctrl + C` 能夠中斷正在執行的指令
|
|
||||||
- ➤ 按下 `Ctrl + C` 能夠產生新的命令提示字串
|
|
||||||
- ➤ 輸入 `echo finish` 來結束這一關
|
|
||||||
|
|
||||||
- id: END
|
|
||||||
task: 結束關卡
|
|
||||||
requirements:
|
|
||||||
- echo-next
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^echo finish$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
恭喜你,你已經完成了這個關卡!
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
echo help
|
|
||||||
ls
|
|
||||||
cat forgotten_scroll.txt
|
|
||||||
echo Hello World
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
title: Hello, Linux World!
|
|
||||||
instruction: |
|
|
||||||
你身在一個漆黑寂靜的地方,似乎失去了記憶。
|
|
||||||
你感到腦中有某個聲音跟你說:
|
|
||||||
「使用 `echo [ 咒語 ]` 指令來朗誦咒語 "help" 」
|
|
||||||
|
|
||||||
requirements:
|
|
||||||
- remove
|
|
||||||
|
|
||||||
stages:
|
|
||||||
- id: help
|
|
||||||
task: 輸入 `echo help` 來朗誦咒語
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
command:
|
|
||||||
- ^echo [Hh]elp$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content:
|
|
||||||
- 你聽到了一個聲音:
|
|
||||||
- type: dialogue
|
|
||||||
speaker: Unix 精靈
|
|
||||||
content: |
|
|
||||||
你好,指令官,我是你的 Unix 精靈。我已在此恭候多時。
|
|
||||||
這個世界是在 3560000 時脈以前,由初代王建立了 Unix 律法,所形成的世界。然而在經過了好幾世代的王後,終於誕生了永恆之王——薩加(Saga),她比歷代的君王都更為強大,且從來不會衰老,使得這個世界變得前所未有的強大。
|
|
||||||
然而,外在世界一直不斷在改變,同時在面臨外敵的不斷攻擊,而薩加卻始終用同一套治理的手段。最終,在 160000 時脈前的一次入侵中,根部遭到攻陷,薩加遭到封印,而這個國家也陷入了黑暗。
|
|
||||||
由於長久以來依賴於薩加,世人已失去了「指令」的力量,即將走向衰敗和滅亡的命運。
|
|
||||||
你的任務,就是找回被世人遺忘的指令,奪回 Linux 之根,成為 Linux 之王。
|
|
||||||
首先,你必須要學會如何使用指令。
|
|
||||||
你要學習的第一個指令,是「搜索」的指令:`ls` (list)。 `ls` 可以用來查看一個目錄中有哪些檔案。
|
|
||||||
現在,請你輸入 `ls` 來查看目前的目錄下有哪些檔案。
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `ls` 指令來查看目前的目錄下有哪些檔案。
|
|
||||||
|
|
||||||
- id: find-scroll
|
|
||||||
task: 搜索卷軸
|
|
||||||
requirements:
|
|
||||||
- help
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
command:
|
|
||||||
- ^ls$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
一瞬間,視野變得清晰起來,你發現自己身處在一個石頭建築的房間裡。
|
|
||||||
你發現了一個檔案,檔名是 `forgotten-scroll.txt`。
|
|
||||||
- type: dialogue
|
|
||||||
speaker: Unix 精靈
|
|
||||||
content: |
|
|
||||||
你找到了一個被遺忘的卷軸,上面寫著解除封印的咒語。
|
|
||||||
使用指令 `cat [ 檔案名稱 ]` (concatenate)來查看檔案內容。
|
|
||||||
我可以為你提供一些指令的提示,只要輸入指令的前幾個字元,然後按下 `Tab` 鍵,就可以看到提示了。
|
|
||||||
例如你只要輸入檔案名稱的前幾個字元,然後按下 `Tab` 鍵,就可以看到提示了。
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `cat [ 檔案名稱 ]` 指令來查看檔案內容。
|
|
||||||
- ➤ 使用 `Tab` 鍵來取得指令提示。
|
|
||||||
- ➤ 如果卡在未知的情況,按下 `Ctrl + C` 嘗試離開。
|
|
||||||
|
|
||||||
- id: read-forgotten-scroll
|
|
||||||
task: 查看卷軸
|
|
||||||
requirements:
|
|
||||||
- find-scroll
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
command:
|
|
||||||
- ^cat forgotten_scroll.txt$
|
|
||||||
responses:
|
|
||||||
- type: dialogue
|
|
||||||
speaker: Unix 精靈
|
|
||||||
content: |
|
|
||||||
你找到的是一個被遺忘的卷軸,上面寫著解除封印的咒語。
|
|
||||||
使用你已經知道的 `echo [ 咒語 ]` 指令來詠唱咒語。
|
|
||||||
hints:
|
|
||||||
- ➤ 輸入 `echo Hello World` 來詠唱咒語。
|
|
||||||
|
|
||||||
- id: END
|
|
||||||
task: 解除封印
|
|
||||||
requirements:
|
|
||||||
- read-forgotten-scroll
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
command:
|
|
||||||
- ^echo [Hh]ello\s*[Ww]orld$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content:
|
|
||||||
- 突然之間,你感覺到一股強大的力量從你的身體中湧出,你的身體開始發光,你的手中出現了一把魔法之劍。
|
|
||||||
- type: dialogue
|
|
||||||
speaker: Unix 精靈
|
|
||||||
content: |
|
|
||||||
恭喜您,我的指令官,您已成功解除了封印。
|
|
||||||
請跟著在接下來的關卡,一步步取回 Linux 之根吧!
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
曾經輝煌的王國 如今被陰霾壟罩
|
|
||||||
Unix之力沉睡著 等待復甦的時機
|
|
||||||
必須不斷的學習 掌握古老的咒語
|
|
||||||
恢復王國的根基 那被遺忘的文明
|
|
||||||
|
|
||||||
用指令念出咒語 Unix之力將甦醒
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
Hello World
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
echo start
|
|
||||||
echo next
|
|
||||||
mv hello.txt notes
|
|
||||||
ls
|
|
||||||
ls notes
|
|
||||||
echo next
|
|
||||||
mv world.txt wow.txt
|
|
||||||
ls
|
|
||||||
mv notes docs
|
|
||||||
ls
|
|
||||||
mv docs/hello.txt hi.txt
|
|
||||||
@@ -1,208 +0,0 @@
|
|||||||
title: 移動檔案
|
|
||||||
instruction: |
|
|
||||||
在這個關卡中,你將會學到:
|
|
||||||
1. 如何使用 `mv` 移動檔案與資料夾
|
|
||||||
|
|
||||||
requirements:
|
|
||||||
- create
|
|
||||||
|
|
||||||
stages:
|
|
||||||
- id: start
|
|
||||||
task: 按下 `開始` 按鈕開始
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^echo start$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
今天我們要學習如何移動檔案與資料夾。
|
|
||||||
現在資料夾裡有一個 `hello.txt` 跟一個 `world.txt`,以及一個 `notes` 資料夾。
|
|
||||||
請先用 `ls` 與 `cat` 來查看它們的內容。
|
|
||||||
看完之後,使用 `echo next` 來繼續吧!
|
|
||||||
hints:
|
|
||||||
- ➤ 用 `ls`、`ls notes` 來查看資料夾內的檔案
|
|
||||||
- ➤ 用 `cat hello.txt` 以及 `cat world.txt` 來查看檔案內容
|
|
||||||
- ➤ 完成後輸入 `echo next` 來繼續
|
|
||||||
|
|
||||||
- id: next
|
|
||||||
requirements:
|
|
||||||
- start
|
|
||||||
task: 查看檔案內容,完成後輸入 `echo next` 來繼續
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^echo next$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
現在我們要來學習如何移動檔案與資料夾。
|
|
||||||
`mv` 指令是移動 move 的縮寫,可以用來移動檔案與資料夾。
|
|
||||||
使用方式為 `mv [來源] [目的地]`。
|
|
||||||
我們先來將 `hello.txt` 移動到 `notes` 資料夾中吧!
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `mv hello.txt notes` 來將 `hello.txt` 移動到 `notes` 資料夾中
|
|
||||||
|
|
||||||
- id: mv-hello
|
|
||||||
requirements:
|
|
||||||
- next
|
|
||||||
task: 將 `hello.txt` 移動到 `notes` 資料夾中
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^mv hello.txt notes$
|
|
||||||
files:
|
|
||||||
- path: /home/commander/notes/hello.txt
|
|
||||||
type: file
|
|
||||||
- path: /home/commander/hello.txt
|
|
||||||
exists: false
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你做得很好!
|
|
||||||
我們用 `ls` 跟 `ls notes` 來查看一下資料夾內的檔案吧!
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `ls`、`ls notes` 來查看資料夾內的檔案
|
|
||||||
|
|
||||||
- id: ls-no-hello
|
|
||||||
requirements:
|
|
||||||
- mv-hello
|
|
||||||
task: 檢查 `hello.txt` 是否還在資料夾中
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
command:
|
|
||||||
- ^ls$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
`hello.txt` 已經不在資料夾中了!
|
|
||||||
|
|
||||||
- id: ls-notes-hello
|
|
||||||
requirements:
|
|
||||||
- mv-hello
|
|
||||||
task: 檢查 `hello.txt` 是否在 `notes` 資料夾中
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
command:
|
|
||||||
- ^ls notes$
|
|
||||||
output:
|
|
||||||
- hello.txt
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
`hello.txt` 出現在 `notes` 資料夾中了!
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
`mv` 指令也可以用來更改檔案名稱。
|
|
||||||
只要將目的地改成新的檔案名稱就可以了。
|
|
||||||
現在我們要將 `world.txt` 更名為 `wow.txt`。
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `mv world.txt wow.txt` 來將 `world.txt` 更名為 `wow.txt`
|
|
||||||
|
|
||||||
- id: mv-world
|
|
||||||
requirements:
|
|
||||||
- ls-notes-hello
|
|
||||||
task: 將 `world.txt` 更名為 `wow.txt`
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^mv
|
|
||||||
files:
|
|
||||||
- path: /home/commander/wow.txt
|
|
||||||
type: file
|
|
||||||
- path: /home/commander/world.txt
|
|
||||||
exists: false
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你做得很好!
|
|
||||||
我們用 `ls` 來確認一下資料夾內的檔案吧!
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `ls` 來查看資料夾內的檔案
|
|
||||||
|
|
||||||
- id: ls-wow
|
|
||||||
requirements:
|
|
||||||
- mv-world
|
|
||||||
task: 檢查 `wow.txt` 是否存在
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
command:
|
|
||||||
- ^ls$
|
|
||||||
output:
|
|
||||||
- wow.txt
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
`world.txt` 現在已經變成 `wow.txt` 了。
|
|
||||||
接下來我們來學習如何移動資料夾吧!
|
|
||||||
移動資料夾非常簡單,方法跟移動檔案一樣。
|
|
||||||
`mv` 指令會自動將資料夾內的所有檔案一起移動
|
|
||||||
我們先來將 `notes` 資料夾改名叫作 `docs` 吧!
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `mv notes docs` 來將 `notes` 資料夾改名叫作 `docs`
|
|
||||||
|
|
||||||
- id: mv-notes
|
|
||||||
requirements:
|
|
||||||
- ls-wow
|
|
||||||
task: 將 `notes` 資料夾改名叫作 `docs`
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^mv
|
|
||||||
files:
|
|
||||||
- path: /home/commander/docs
|
|
||||||
type: directory
|
|
||||||
- path: /home/commander/notes
|
|
||||||
exists: false
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你做得很好!
|
|
||||||
我們用 `ls` 來確認一下資料夾內的檔案吧!
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `ls` 來查看資料夾內的檔案
|
|
||||||
|
|
||||||
- id: ls-docs
|
|
||||||
requirements:
|
|
||||||
- mv-notes
|
|
||||||
task: 檢查 `docs` 資料夾是否存在
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
command:
|
|
||||||
- ^ls$
|
|
||||||
output:
|
|
||||||
- docs
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
`notes` 資料夾已經變成 `docs` 了。
|
|
||||||
那麼最後來考考你:
|
|
||||||
試著將 `docs/hello.txt` 移動到家目錄底下的 `hi.txt`。
|
|
||||||
如果你不知道怎麼做,可以輸入 `echo hint` 來取得提示。
|
|
||||||
|
|
||||||
- id: END
|
|
||||||
requirements:
|
|
||||||
- ls-docs
|
|
||||||
task: 將 `docs/hello.txt` 移動到家目錄底下的 `hi.txt`
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^mv
|
|
||||||
files:
|
|
||||||
- path: /home/commander/hi.txt
|
|
||||||
type: file
|
|
||||||
- path: /home/commander/docs/hello.txt
|
|
||||||
exists: false
|
|
||||||
exceptions:
|
|
||||||
- condition:
|
|
||||||
command:
|
|
||||||
- ^echo hint$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你可以使用 `mv docs/hello.txt hi.txt` 來完成這個任務。
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `mv docs/hello.txt hi.txt` 來將 `docs/hello.txt` 移動到家目錄底下的 `hi.txt`
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
你做得很好!
|
|
||||||
你已經學會如何移動檔案與資料夾了!
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Hello
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Note
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
World
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
echo start
|
|
||||||
ls
|
|
||||||
cd folder1
|
|
||||||
cd ../folder2
|
|
||||||
ls ../folder1
|
|
||||||
pwd
|
|
||||||
ls ~
|
|
||||||
cd ~
|
|
||||||
@@ -1,168 +0,0 @@
|
|||||||
title: 檔案路徑
|
|
||||||
instruction: |
|
|
||||||
在這個關卡中,你將會學到:
|
|
||||||
1. 絕對路徑和相對路徑
|
|
||||||
2. 如何使用 `pwd` 顯示目前的路徑
|
|
||||||
|
|
||||||
requirements:
|
|
||||||
- cast
|
|
||||||
- explore
|
|
||||||
|
|
||||||
stages:
|
|
||||||
- id: start
|
|
||||||
task: 按下 `開始` 按鈕開始教程
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^echo start$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
在 Linux 系統中,每個資料夾和檔案都有獨一無二的路徑。
|
|
||||||
而路徑又分為相對路徑和絕對路徑。
|
|
||||||
相對路徑是從目前所在的資料夾開始的路徑,而絕對路徑則是從根目錄開始的路徑。
|
|
||||||
請你先用 `ls` 指令查看這個資料夾中的檔案。
|
|
||||||
|
|
||||||
- id: ls
|
|
||||||
task: 查看資料夾中的檔案
|
|
||||||
requirements:
|
|
||||||
- start
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
command:
|
|
||||||
- ^ls$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
現在你使用 `cd` 指令進入 `folder1` 資料夾。
|
|
||||||
|
|
||||||
- id: cd-folder1
|
|
||||||
task: 進入 `folder1` 資料夾
|
|
||||||
requirements:
|
|
||||||
- ls
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander/folder1$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
你進入了 `folder1` 資料夾。
|
|
||||||
在進入 `folder1` 資料夾時,使用的 `cd folder1` 中的 `folder1` 就是相對路徑。
|
|
||||||
相同的道理 `cd ..` 中的 `..` 也是相對路徑。
|
|
||||||
相對路徑可以不只有一層資料夾。
|
|
||||||
例如:現在想要進入 `folder2` 資料夾的話,可以使用 `cd ..` 回到上一層資料夾,再使用 `cd folder2` 進入 `folder2` 資料夾。
|
|
||||||
然而,你也可以用 `/` 將 `..` 和 `folder2` 依序連接起來,使用 `cd ../folder2` 來一次進入 `folder2` 資料夾。
|
|
||||||
現在試著用這個方式進入 `folder2` 資料夾看看吧。
|
|
||||||
hints:
|
|
||||||
- ➤ 請輸入 `cd ../folder2` 來進入 `folder2` 資料夾。
|
|
||||||
|
|
||||||
- id: cd-folder2
|
|
||||||
task: 進入 `folder2` 資料夾
|
|
||||||
requirements:
|
|
||||||
- cd-folder1
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander/folder2$
|
|
||||||
exceptions:
|
|
||||||
- condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
輸入 `cd folder2` 來進入 `folder2` 資料夾
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
你成功的用相對路徑進入了 `folder2` 資料夾。
|
|
||||||
而你也能夠在 `ls` 後加上相對路徑來查看其他資料夾的內容。
|
|
||||||
例如:現在你想要查看 `folder1` 資料夾中有哪些檔案,你可以使用 `ls ../folder1` 來查看。
|
|
||||||
試著用這個方式查看 `folder1` 資料夾中的檔案吧。
|
|
||||||
hints:
|
|
||||||
- ➤ 請輸入 `ls ../folder1` 來查看 `folder1` 資料夾中的檔案。
|
|
||||||
|
|
||||||
- id: ls-folder1
|
|
||||||
task: 查看 `folder1` 資料夾中的檔案
|
|
||||||
requirements:
|
|
||||||
- cd-folder2
|
|
||||||
condition:
|
|
||||||
output:
|
|
||||||
- file1.txt
|
|
||||||
exceptions:
|
|
||||||
- condition:
|
|
||||||
not:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander/folder2$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
進到 `folder1` 資料夾並用 `ls` 查看內容。
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
很好,你現在知道如何運用相對路徑了。
|
|
||||||
接下來要介紹的是絕對路徑。
|
|
||||||
絕對路徑是從根目錄開始的路徑,根目錄是位於檔案樹的最上層的資料夾。
|
|
||||||
你可以使用 `pwd` 來查看目前所在的絕對路徑。
|
|
||||||
`pwd` 的意思是 `print working directory`,也就是顯示目前所在的路徑。
|
|
||||||
現在請你使用 `pwd` 來查看目前所在的絕對路徑。
|
|
||||||
hints:
|
|
||||||
- ➤ 輸入 `pwd` 查看目前所在的絕對路徑
|
|
||||||
|
|
||||||
- id: pwd
|
|
||||||
task: 查看目前所在的絕對路徑
|
|
||||||
requirements:
|
|
||||||
- ls-folder1
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander/folder2$
|
|
||||||
command:
|
|
||||||
- ^pwd$
|
|
||||||
exceptions:
|
|
||||||
- condition:
|
|
||||||
not:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander/folder2$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
請先回到 `folder2` 資料夾,並用 `pwd` 來查看目前所在的絕對路徑
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
你可以看到你現在的絕對路徑是 `/home/commander/folder2`。
|
|
||||||
而絕對路徑其實也顯示在你的命令提示符中。
|
|
||||||
`commander:~/folder2 $` 中的 `~/folder2` 就是你的絕對路徑。
|
|
||||||
而 `~` 則是 `/home/commander` 這個絕對路徑的縮寫,又稱為家目錄,你每次遊戲都會從這裡開始。
|
|
||||||
`ls` 和 `cd` 指令也可以使用絕對路徑。
|
|
||||||
例如:你可以使用 `ls ~` 來查看家目錄的內容,或使用 `cd ~` 來回到家目錄。
|
|
||||||
現在請你使用 `ls ~` 來查看家目錄的內容。
|
|
||||||
hints:
|
|
||||||
- ➤ 輸入 `ls ~` 來查看家目錄的內容
|
|
||||||
|
|
||||||
- id: ls-~
|
|
||||||
task: 查看家目錄的內容
|
|
||||||
requirements:
|
|
||||||
- pwd
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^ls ~$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
現在請使用 `cd ~` 回到家目錄。
|
|
||||||
hints:
|
|
||||||
- ➤ 輸入 `cd ~` 來回到家目錄
|
|
||||||
|
|
||||||
- id: END
|
|
||||||
task: 回到家目錄
|
|
||||||
requirements:
|
|
||||||
- ls-~
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^cd ~$
|
|
||||||
exceptions:
|
|
||||||
- condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
請使用 `cd ~` 回到家目錄
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
你回到家目錄了。
|
|
||||||
恭喜!現在你已經知道如何運用絕對路徑和相對路徑,以及查看目前所在的絕對路徑了。
|
|
||||||
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
@@ -1,5 +0,0 @@
|
|||||||
echo start
|
|
||||||
ls
|
|
||||||
cat file1.txt
|
|
||||||
ls folder
|
|
||||||
cat folder/file.txt
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
title: 查看檔案內容
|
|
||||||
instruction: |
|
|
||||||
在這個關卡中,你將會學到:
|
|
||||||
1. 如何使用 `cat` 查看檔案的內容
|
|
||||||
|
|
||||||
requirements:
|
|
||||||
- path
|
|
||||||
|
|
||||||
stages:
|
|
||||||
- id: start
|
|
||||||
task: 按下 `開始` 按鈕開始教程
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^echo start$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
在終端機中,並沒有圖形介面可以讓你查看檔案的內容。
|
|
||||||
但是你可以透過 `cat` 指令來查看檔案的內容。
|
|
||||||
`cat` 指令是 concatenate 的縮寫,也就是串接的意思。
|
|
||||||
在輸入 `cat [檔案名稱]` 指令後,終端機會輸出檔案的內容。
|
|
||||||
請你先查看這個資料夾中哪些檔案。
|
|
||||||
hints:
|
|
||||||
- ➤ 請輸入 `ls` 來查看這個資料夾中的檔案。
|
|
||||||
|
|
||||||
- id: ls-~
|
|
||||||
task: 查看資料夾中的檔案
|
|
||||||
requirements:
|
|
||||||
- start
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
command:
|
|
||||||
- ^ls$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
這個資料夾中有一個檔案和一個資料夾。
|
|
||||||
現在你可以使用 `cat` 指令來查看 `file1.txt` 的內容。
|
|
||||||
hints:
|
|
||||||
- ➤ 請輸入 `cat file1.txt` 來查看檔案的內容。
|
|
||||||
|
|
||||||
- id: cat-file1
|
|
||||||
task: 查看 `file1.txt` 的內容
|
|
||||||
requirements:
|
|
||||||
- ls-~
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
command:
|
|
||||||
- ^cat file1.txt$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
你應該能看到檔案的內容了。
|
|
||||||
`cat` 指令也可以使用路徑來查看檔案內容
|
|
||||||
請你先用 `ls folder` 查看資料夾內的檔案。
|
|
||||||
hints:
|
|
||||||
- ➤ 輸入 `ls folder` 查看資料夾內的檔案
|
|
||||||
|
|
||||||
- id: ls-folder
|
|
||||||
task: 查看 `folder` 資料夾
|
|
||||||
requirements:
|
|
||||||
- cat-file1
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
command:
|
|
||||||
- ^ls folder$
|
|
||||||
exceptions:
|
|
||||||
- condition:
|
|
||||||
not:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
請留在 `/home/commander` 資料夾中。
|
|
||||||
請輸入 `cd ~` 回到 `/home/commander` 資料夾。
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
你現在知道 `folder` 資料夾的內容了。
|
|
||||||
請用 `cat` 指令來查看 `folder` 資料夾中的 `file.txt` 檔案。
|
|
||||||
hints:
|
|
||||||
- ➤ 輸入 `cat folder/file.txt` 查看 `file.txt` 檔案的內容。
|
|
||||||
|
|
||||||
- id: END
|
|
||||||
task: 查看 `file.txt` 的內容
|
|
||||||
requirements:
|
|
||||||
- ls-folder
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
command:
|
|
||||||
- ^cat folder/file.txt$
|
|
||||||
exceptions:
|
|
||||||
- condition:
|
|
||||||
not:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
responses:
|
|
||||||
- type: narrative
|
|
||||||
content: |
|
|
||||||
請留在 `/home/commander` 資料夾中。
|
|
||||||
請輸入 `cd ~` 回到 `/home/commander` 資料夾。
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
你成功地查看到 `file.txt` 的內容了。
|
|
||||||
你已經會使用 `cat` 指令查看檔案的內容了。
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
This is file1.txt
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
This is file.txt
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
echo start
|
|
||||||
ls
|
|
||||||
rm hello.txt
|
|
||||||
ls
|
|
||||||
rm world.txt wow.txt
|
|
||||||
ls
|
|
||||||
rmdir empty
|
|
||||||
ls
|
|
||||||
rmdir notes
|
|
||||||
rm notes/note.txt
|
|
||||||
rmdir notes
|
|
||||||
ls trash
|
|
||||||
rm -r trash
|
|
||||||
ls
|
|
||||||
@@ -1,259 +0,0 @@
|
|||||||
title: 刪除檔案與資料夾
|
|
||||||
instruction: |
|
|
||||||
在這個關卡中,你將會學到:
|
|
||||||
1. 如何使用 `rm` 指令刪除檔案
|
|
||||||
2. 如何使用 `rmdir` 刪除空資料夾
|
|
||||||
3. 如何使用 `rm -r` 刪除資料夾與其中的所有檔案
|
|
||||||
|
|
||||||
requirements:
|
|
||||||
- copy
|
|
||||||
- move
|
|
||||||
|
|
||||||
stages:
|
|
||||||
- id: start
|
|
||||||
task: 按下 `開始` 按鈕開始
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^echo start$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
今天我們要學習如何刪除檔案與資料夾。
|
|
||||||
請先使用 `ls` 指令查看資料夾內容。
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `ls` 來查看資料夾內容。
|
|
||||||
|
|
||||||
- id: ls
|
|
||||||
requirements:
|
|
||||||
- start
|
|
||||||
task: 使用 `ls` 查看資料夾內容
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^ls$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
首先要來學習 `rm` 指令。`rm` 指令可以用來刪除檔案。使用方式是 `rm [檔案名稱]`。
|
|
||||||
現在請你使用 `rm` 指令刪除 `hello.txt`。
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `rm hello.txt` 來刪除 `hello.txt`。
|
|
||||||
|
|
||||||
- id: rm-hello
|
|
||||||
requirements:
|
|
||||||
- ls
|
|
||||||
task: 使用 `rm` 刪除 `hello.txt`
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^rm\s
|
|
||||||
files:
|
|
||||||
- path: /home/commander/hello.txt
|
|
||||||
exists: false
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
很好!現在請你使用 `ls` 指令來查看資料夾內容。
|
|
||||||
|
|
||||||
- id: ls-no-hello
|
|
||||||
requirements:
|
|
||||||
- rm-hello
|
|
||||||
task: 使用 `ls` 查看資料夾內容
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^ls$
|
|
||||||
files:
|
|
||||||
- path: /home/commander/hello.txt
|
|
||||||
exists: false
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
你會發現 `hello.txt` 已經不見了。
|
|
||||||
`rm` 也可以同時刪除多個檔案,只要把檔案名稱用空白隔開就可以了,也就是 `rm [檔案1] [檔案2] ...`。
|
|
||||||
現在請你使用 `rm` 刪除 `world.txt` 與 `wow.txt`。
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `rm world.txt wow.txt` 來刪除 `world.txt` 與 `wow.txt`。
|
|
||||||
- ➤ 檔案名稱之間要用空白隔開。
|
|
||||||
- ➤ 名稱的順序不重要。
|
|
||||||
|
|
||||||
- id: rm-world-wow
|
|
||||||
requirements:
|
|
||||||
- ls-no-hello
|
|
||||||
task: 使用 `rm` 刪除 `world.txt` 與 `wow.txt`
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^rm
|
|
||||||
files:
|
|
||||||
- path: /home/commander/world.txt
|
|
||||||
exists: false
|
|
||||||
- path: /home/commander/wow.txt
|
|
||||||
exists: false
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
很好!我們一樣用 `ls` 指令確認一下資料夾內容。
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `ls` 來查看資料夾內容。
|
|
||||||
|
|
||||||
- id: ls-no-world-wow
|
|
||||||
requirements:
|
|
||||||
- rm-world-wow
|
|
||||||
task: 使用 `ls` 查看資料夾內容
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^ls$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
你會發現 `world.txt` 與 `wow.txt` 已經不見了。
|
|
||||||
現在我們來學習 `rmdir` 指令。`rmdir` 指令可以用來刪除資料夾。使用方式是 `rmdir [資料夾名稱]`。
|
|
||||||
現在請你使用 `rmdir` 指令刪除 `empty` 資料夾。
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `rmdir empty` 來刪除 `empty` 資料夾。
|
|
||||||
|
|
||||||
- id: rm-empty
|
|
||||||
requirements:
|
|
||||||
- ls-no-world-wow
|
|
||||||
task: 使用 `rmdir` 刪除 `empty` 資料夾
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^rmdir empty$
|
|
||||||
files:
|
|
||||||
- path: /home/commander/empty
|
|
||||||
exists: false
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
很好!現在請你使用 `ls` 指令來查看資料夾內容。
|
|
||||||
|
|
||||||
- id: ls-no-empty
|
|
||||||
requirements:
|
|
||||||
- rm-empty
|
|
||||||
task: 使用 `ls` 查看資料夾內容
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^ls$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
你會發現 `empty` 資料夾已經不見了。
|
|
||||||
現在請你試試看 `rmdir` 指令刪除 `notes` 資料夾。
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `rmdir notes` 來刪除 `notes` 資料夾
|
|
||||||
|
|
||||||
- id: rm-notes-failed
|
|
||||||
requirements:
|
|
||||||
- ls-no-empty
|
|
||||||
task: 使用 `rmdir` 刪除 `notes` 資料夾
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^rmdir notes$
|
|
||||||
files:
|
|
||||||
- path: /home/commander/notes
|
|
||||||
type: folder
|
|
||||||
exists: true
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
指令執行失敗了!
|
|
||||||
你會發現 `notes` 資料夾還在。
|
|
||||||
這是因為 `rmdir` 指令只能刪除空的資料夾。
|
|
||||||
我們先來將 `notes` 資料夾的檔案刪除再來試一次。
|
|
||||||
現在請你使用 `rm` 指令刪除 `notes` 資料夾內的檔案。
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `rm notes/[檔案名稱]` 來刪除 `notes` 資料夾內的檔案。
|
|
||||||
- ➤ 你可能需要使用 `ls notes` 來查看 `notes` 資料夾內有哪些檔案。
|
|
||||||
|
|
||||||
- id: rm-notes-content
|
|
||||||
requirements:
|
|
||||||
- rm-notes-failed
|
|
||||||
task: 使用 `rm` 刪除 `notes` 資料夾內的檔案
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^rm
|
|
||||||
files:
|
|
||||||
- path: /home/commander/notes
|
|
||||||
type: folder
|
|
||||||
exists: true
|
|
||||||
- path: /home/commander/notes/note.txt
|
|
||||||
exists: false
|
|
||||||
exceptions:
|
|
||||||
- condition:
|
|
||||||
command:
|
|
||||||
- ^rm notes/\[.*\]
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
你好像理解錯了。
|
|
||||||
這邊中括號包起來 `[檔案名稱]` 是參數的意思,需要你自己填入檔案的名稱。
|
|
||||||
實際打指令的時候,不要打中括號 `[]` 喔!
|
|
||||||
例如,如果要刪除 `notes` 資料夾內的 `note.txt` 檔案,指令應該要是 `rm notes/note.txt`。
|
|
||||||
請先用 `ls notes` 確認 `notes` 資料夾內有哪些檔案,再來試試看吧!
|
|
||||||
- condition:
|
|
||||||
command:
|
|
||||||
- ^rmdir notes$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
這個錯誤的意思是找不到檔案
|
|
||||||
請先用 `ls notes` 確認 `notes` 資料夾內有哪些檔案,再來試試看吧!
|
|
||||||
- condition:
|
|
||||||
command:
|
|
||||||
- ^ls notes$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
`notes` 資料夾中有著 `note.txt` 檔案。
|
|
||||||
那我們就使用 `rm notes/note.txt` 來刪除 `note.txt` 檔案吧!
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
很好!現在我們再使用 `rmdir` 指令刪除 `notes` 資料夾。
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `rmdir notes` 來刪除 `notes` 資料夾。
|
|
||||||
|
|
||||||
- id: rm-notes
|
|
||||||
requirements:
|
|
||||||
- rm-notes-content
|
|
||||||
task: 使用 `rmdir` 刪除 `notes` 資料夾
|
|
||||||
condition:
|
|
||||||
files:
|
|
||||||
- path: /home/commander/notes
|
|
||||||
exists: false
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
很好!`notes` 資料夾已經被刪除了。
|
|
||||||
接下來我們來刪除 `trash` 資料夾。
|
|
||||||
先使用 `ls` 指令查看一下 `trash` 資料夾內容。
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `ls trash` 來查看 `trash` 中的檔案。
|
|
||||||
|
|
||||||
- id: ls-trash
|
|
||||||
requirements:
|
|
||||||
- rm-notes
|
|
||||||
task: 使用 `ls` 查看 `trash` 資料夾內容
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^ls trash$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
天啊!`trash` 資料夾裡有好多檔案,甚至還有其他的資料夾!
|
|
||||||
如果今天資料夾中有很多檔案與資料夾,我們不想一個一個刪除,該怎麼辦呢?
|
|
||||||
跟 `cp` 的時候一樣,這時候我們可以加上 `-r` 的選項來刪除資料夾與它的所有檔案。
|
|
||||||
`-r` 的意思是遞迴(recursive),也就是會把資料夾中的所有檔案都刪除。
|
|
||||||
我們來試試看吧!
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `rm -r trash` 來刪除 `trash` 資料夾。
|
|
||||||
|
|
||||||
- id: rm-trash
|
|
||||||
requirements:
|
|
||||||
- ls-trash
|
|
||||||
task: 使用 `rm -r` 刪除 `trash` 資料夾
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^rm -r trash$
|
|
||||||
files:
|
|
||||||
- path: /home/commander/trash
|
|
||||||
exists: false
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
很好!現在請你使用 `ls` 指令查看資料夾內容。
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `ls` 來查看資料夾內容。
|
|
||||||
|
|
||||||
- id: END
|
|
||||||
requirements:
|
|
||||||
- rm-trash
|
|
||||||
task: 使用 `ls` 查看資料夾內容
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^ls$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
`trash` 資料夾已經不見了。
|
|
||||||
恭喜你!你已經學會如何刪除檔案與資料夾了!
|
|
||||||
Whitespace-only changes.
@@ -1 +0,0 @@
|
|||||||
Hello
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Note
|
|
||||||
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
@@ -1 +0,0 @@
|
|||||||
World
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Wow
|
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
title: Tab 自動補全
|
|
||||||
instruction: |
|
|
||||||
在這個關卡,你將會學到
|
|
||||||
|
|
||||||
1. 如何使用 Tab 自動補全路徑
|
|
||||||
2. 出現衝突時如何處理
|
|
||||||
|
|
||||||
按下中間的按鈕來開始這個關卡。
|
|
||||||
|
|
||||||
requirements:
|
|
||||||
- read
|
|
||||||
|
|
||||||
stages:
|
|
||||||
- id: start
|
|
||||||
task: 按下開始按鈕開始關卡
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^echo start$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
在終端機裡,所有的操作都是用文字進行。
|
|
||||||
然而,打字很花時間,而且只要一個字打錯,指令就會執行失敗。
|
|
||||||
因些終端機提供「自動補全」的功能,自動幫你完成檔案路徑、指令名稱、參數名稱,節省你的時間,也避免輸入錯誤。
|
|
||||||
|
|
||||||
在開始練習之前,我們先來看看家目錄下有哪些檔案。
|
|
||||||
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `ls` 查看檔案列表
|
|
||||||
|
|
||||||
- id: ls
|
|
||||||
task: 使用 `ls` 查看檔案列表
|
|
||||||
requirements:
|
|
||||||
- start
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^ls$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
你看到了這個資料夾中充滿了超級長和有錯字的檔案名稱,如果這些都要我們一個一個字輸入,肯定會很痛苦。
|
|
||||||
但不要緊,只要使用自動補全,檔名不論多長都能輕鬆輸入。
|
|
||||||
|
|
||||||
使用自動補全的方法是,**先輸入部分的檔名,再按下 Tab**
|
|
||||||
(Tab 是 Q 左邊的按鍵)
|
|
||||||
例如要補全 `SuperSecretPlansForWorldDomination.txt`,你可以先輸入 `Su` 再按下 Tab,終端機就會幫你打完剩下的檔名了。
|
|
||||||
|
|
||||||
我們試看看印出 `SuperSecretPlansForWorldDomination.txt` 這個檔案的內容。
|
|
||||||
|
|
||||||
hints:
|
|
||||||
- ➤ 使用 `cat SuperSecretPlansForWorldDomination.txt` 來印出檔案內容
|
|
||||||
- ➤ 在輸入 `cat Su` 之後按下 Tab 來自動補全檔名,完成後按 Enter 就能執行指令
|
|
||||||
|
|
||||||
- id: cat_super_secret
|
|
||||||
task: 使用 `cat` 印出 SuperSecretPlansForWorldDomination.txt 的內容
|
|
||||||
requirements:
|
|
||||||
- ls
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
command:
|
|
||||||
- ^cat SuperSecretPlansForWorldDomination.txt$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
你看到了終端機自動幫你補全了 `SuperSecretPlansForWorldDomination.txt`,就算是很長的檔名,也不用怕打錯字。
|
|
||||||
開頭不論你打了幾個字都無所謂,你可以只打 `S`、`Su`、`Sup`、`Super` 或甚至完全不打字都沒關係。
|
|
||||||
但是你打的字越少,就越容易跟其他的檔案重複,這時候就要做衝突處理。
|
|
||||||
關於衝突處理,我們會在待會練習。
|
|
||||||
|
|
||||||
自動補全也可以用來對付拼字錯誤,像是 `ThsisAFlieNmaeWthAloOfTpyosThatisRleayHdToRaed.txt` 這個檔案充滿了錯字,任誰來打都難保不會失手。
|
|
||||||
這一次,請你一樣用 `cat` 來試著印出 `ThsisAFlieNmaeWthAloOfTpyosThatisRleayHdToRaed.txt` 的內容。
|
|
||||||
|
|
||||||
hints:
|
|
||||||
- ➤ 輸入 `cat ThsisAFlieNmaeWthAloOfTpyosThatisRleayHdToRaed.txt` 來印出檔案內容
|
|
||||||
- ➤ 輸入 `cat Thsi` 之後按下 Tab 做自動補全
|
|
||||||
- ➤ 試試看輸入更少的字元就按 Tab 會有什麼樣的結果
|
|
||||||
|
|
||||||
- id: cat_typo
|
|
||||||
task: 使用 `cat` 印出 ThsisAFlieNmaeWthAloOfTpyosThatisRleayHdToRaed.txt 的內容
|
|
||||||
requirements:
|
|
||||||
- cat_super_secret
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
command:
|
|
||||||
- ^cat ThsisAFlieNmaeWthAloOfTpyosThatisRleayHdToRaed.txt$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
你看到了這個充滿錯字的檔案了。
|
|
||||||
只要使用自動補全,不管是錯字、大小寫變化、還是隨機亂碼都不再是困擾了。
|
|
||||||
讓我們再練習一個,這次請你查看 `ThingsIWillOrganizeLaterWhenIHaveTime` 資料夾中的檔案。
|
|
||||||
一樣,輸入前幾個字,然後按 Tab 自動補全。
|
|
||||||
|
|
||||||
hints:
|
|
||||||
- ➤ 輸入 `ls ThingsIWillOrganizeLaterWhenIHaveTime` 查看資料夾中的檔案
|
|
||||||
- ➤ 輸入 `ls Thing` 之後按下 Tab 做自動補全
|
|
||||||
|
|
||||||
- id: ls_things
|
|
||||||
task: 使用 `ls` 查看 ThingsIWillOrganizeLaterWhenIHaveTime 資料夾中的檔案
|
|
||||||
requirements:
|
|
||||||
- cat_typo
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
command:
|
|
||||||
- ^ls ThingsIWillOrganizeLaterWhenIHaveTime$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
你看到了這個資料夾裡有著三個檔案:一個如何做三明治的教學跟兩個在半夜隨機下載的東西。
|
|
||||||
假如我們要打開三明治教學 `HowToProperlyMakeASandwich.txt`,但是它位在資料夾中,我們沒辦法只按一次 Tab 就補全它的檔名。
|
|
||||||
這次我們就要來練習使用**連續的自動補全**。
|
|
||||||
|
|
||||||
像這樣檔案位在其他資料夾的情況,我們只要先輸入資料夾名稱的開頭,按一次 Tab,再輸入檔案的開頭,再按一次 Tab,就能夠補全 `資料夾/檔名` 了。
|
|
||||||
而且當你補全的對象是資料夾時,終端機還會自動幫你在結尾加上 `/`,省去你自己輸入的時間。
|
|
||||||
那麼我們就來打開 `HowToProperlyMakeASandwich.txt`。
|
|
||||||
|
|
||||||
hints:
|
|
||||||
- ➤ 輸入 `cat ThingsIWillOrganizeLaterWhenIHaveTime/HowToProperlyMakeASandwich.txt` 來印出檔案內容
|
|
||||||
- ➤ 先輸入 `cat Thin` 按 Tab 補全至 `cat ThingsIWillOrganizeLaterWhenIHaveTime/`
|
|
||||||
- ➤ 再輸入 `cat ThingsIWillOrganizeLaterWhenIHaveTime/How` 按 Tab 補全至完整路徑
|
|
||||||
|
|
||||||
- id: cat_sandwich
|
|
||||||
task: 使用 `cat` 印出 ThingsIWillOrganizeLaterWhenIHaveTime/HowToProperlyMakeASandwich.txt 的內容
|
|
||||||
requirements:
|
|
||||||
- ls_things
|
|
||||||
condition:
|
|
||||||
command:
|
|
||||||
- ^cat ThingsIWillOrganizeLaterWhenIHaveTime/HowToProperlyMakeASandwich.txt$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
你看到了這份嚴謹的三明治製作教學。
|
|
||||||
那我們剩下來要看的就是資料夾裡的兩個檔案:`RandomStuffThatIDownloadedAt3AM.txt` 和 `RandomStuffThatIDownloadedAt4AM.txt`
|
|
||||||
兩個檔名非常相似,只有最後一個是 3AM 另一個是 4AM。
|
|
||||||
這樣我們只打前面幾個字的時候,沒有辦法分辦到底選的是哪一個檔案。
|
|
||||||
這就是**發生衝突**。
|
|
||||||
|
|
||||||
遇到衝突的解決方法,其實跟使用資料夾是一樣的。
|
|
||||||
在輸入 `Ran` 按下 Tab 後,終端機會補全到衝突發生前的位置,也就是 `RandomStuffThatIDownloadedAt`。
|
|
||||||
我們只要根據我們想開啟的檔案,輸入 `3` 或是 `4`,再按一下 Tab,就能夠補全剩下的檔名了。
|
|
||||||
|
|
||||||
_如果我們把完整的路徑視作補全的對象,那資料夾其實就是一種衝突的情況。_
|
|
||||||
|
|
||||||
hints:
|
|
||||||
- ➤ 印出 `ThingsIWillOrganizeLaterWhenIHaveTime/RandomStuffThatIDownloadedAt3AM.txt` 的檔案內容
|
|
||||||
- ➤ 先輸入 `cat Thin` 按 Tab 補全至 `cat ThingsIWillOrganizeLaterWhenIHaveTime/`
|
|
||||||
- ➤ 再接續輸入 `Ran` 按 Tab 補全至 `cat ThingsIWillOrganizeLaterWhenIHaveTime/RandomStuffThatIDownloadedAt`
|
|
||||||
- ➤ 再接續輸入 `3` 後按 Tab 補全至完整的路徑。
|
|
||||||
|
|
||||||
- id: cat_3am
|
|
||||||
task: 使用 `cat` 印出 ThingsIWillOrganizeLaterWhenIHaveTime/RandomStuffThatIDownloadedAt3AM.txt 的內容
|
|
||||||
requirements:
|
|
||||||
- cat_sandwich
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander$
|
|
||||||
command:
|
|
||||||
- ^cat ThingsIWillOrganizeLaterWhenIHaveTime/RandomStuffThatIDownloadedAt3AM.txt$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
你看到了一份凌晨三點下載的檔案列表。嗯…我今天網路用的夠多了。
|
|
||||||
最後留下一個挑戰給你,請先進入到 `ThingsIWillOrganizeLaterWhenIHaveTime` 資料夾後,再印出 `RandomStuffThatIDownloadedAt4AM.txt`。
|
|
||||||
練習使用今天教的自動補全來完成任務。
|
|
||||||
|
|
||||||
hints:
|
|
||||||
- ➤ 先使用 `cd` 進入 ThingsIWillOrganizeLaterWhenIHaveTime 資料夾
|
|
||||||
- ➤ 再使用 `cat` 印出 RandomStuffThatIDownloadedAt4AM.txt 的內容
|
|
||||||
- ➤ 記得使用 Tab 自動補全來節省時間和避免錯誤
|
|
||||||
|
|
||||||
- id: cd_things
|
|
||||||
task: 使用 `cd` 進入 ThingsIWillOrganizeLaterWhenIHaveTime 資料夾
|
|
||||||
requirements:
|
|
||||||
- cat_3am
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander/ThingsIWillOrganizeLaterWhenIHaveTime$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
很好,你已經成功進入 ThingsIWillOrganizeLaterWhenIHaveTime 資料夾了。
|
|
||||||
現在請使用 `cat` 指令來印出 `RandomStuffThatIDownloadedAt4AM.txt` 的內容。
|
|
||||||
記得運用 Tab 自動補全功能!
|
|
||||||
|
|
||||||
- id: END
|
|
||||||
task: 使用 `cat` 印出 RandomStuffThatIDownloadedAt4AM.txt 的內容
|
|
||||||
requirements:
|
|
||||||
- cd_things
|
|
||||||
condition:
|
|
||||||
pwd:
|
|
||||||
- ^/home/commander/ThingsIWillOrganizeLaterWhenIHaveTime$
|
|
||||||
command:
|
|
||||||
- ^cat RandomStuffThatIDownloadedAt4AM.txt$
|
|
||||||
responses:
|
|
||||||
- content: |
|
|
||||||
恭喜你!你已經成功完成了所有的任務,並且熟練地運用了 Tab 自動補全功能。
|
|
||||||
這個技能將會在你未來的終端機操作中帶來極大的便利。
|
|
||||||
記住,不論是長檔名、複雜路徑,還是難以拼寫的檔案名稱,Tab 自動補全都是你的好幫手!
|
|
||||||
|
|
||||||
hints:
|
|
||||||
- ➤ 你已經完成了所有任務!可以繼續探索或是結束這個關卡。
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
1. 收集全世界的襪子,讓所有人都找不到另一支襪子。
|
|
||||||
2. 訓練一支企鵝軍隊,用可愛征服世界。
|
|
||||||
3. 發明一種能讓所有人講話都變成雞叫聲的裝置。
|
|
||||||
4. 把所有的咖啡替換成無咖啡因的,看著世界陷入混亂。
|
|
||||||
5. 用巨大的橡皮擦擦掉所有國家的邊界,宣布地球是一個巨大的遊樂場。
|
|
||||||
6. 讓所有政治家在演講時必須穿著小丑服裝。
|
|
||||||
7. 把所有的數學課本改成烹飪食譜,看看會發生什麼。
|
|
||||||
8. 發明一種能讓人說出真心話的香水,然後在聯合國大會上噴灑。
|
|
||||||
9. 用巨大的磁鐵吸走地球上所有的金屬,建造一個太空迪士尼樂園。
|
|
||||||
10. 最後,如果以上都失敗了,就用可愛貓咪影片轟炸全世界的社交媒體,直到所有人都投降為止。
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
如何"正確"製作一個三明治:
|
|
||||||
|
|
||||||
1. 用力拍醒你的麵包。它們整天都在發懶!
|
|
||||||
2. 仔細選擇配料,確保每種都有自己的個性。例如,挑選一片特別愛說笑話的火腿。
|
|
||||||
3. 用尺精確測量每片配料的厚度,誤差不得超過1毫米。
|
|
||||||
4. 在組裝三明治時,必須戴上白手套和實驗室防護鏡,以確保最高級別的衛生。
|
|
||||||
5. 完成後,用放大鏡仔細檢查每一寸三明治,確保完美無缺。
|
|
||||||
|
|
||||||
記住:製作三明治是一門精確的科學,絕不容許有任何差錯或創意!
|
|
||||||
-5
@@ -1,5 +0,0 @@
|
|||||||
我在凌晨 3 點開始下載的東西,但我一直沒有時間去整理。
|
|
||||||
|
|
||||||
1. 一個可以把所有食物變成披薩的魔法棒設計圖
|
|
||||||
2. 如何在五分鐘內學會十種外星語言的秘密指南
|
|
||||||
3. 用舊襪子製作時尚帽子的教程視頻(連結已失效)
|
|
||||||
-5
@@ -1,5 +0,0 @@
|
|||||||
我在凌晨 4 點開始下載的東西,但我一直沒有時間去整理。
|
|
||||||
|
|
||||||
1. 一份詳細解釋為什麼貓咪才是地球真正統治者的論文
|
|
||||||
2. 如何用口香糖和迴紋針修理國際太空站的WikiHow頁面
|
|
||||||
3. 訓練水母成為水下偵探的完整課程大綱
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
Congrats! You fond a flie namd by somone who cleerly had a strok. If you can raed this, you mght be a geenius or jsut realy gud at Wheel of Fortnue.
|
|
||||||
|
|
||||||
Remeber: freinds don't let freinds name files aftr drinkng too much. But hey, at lest you used tab compltion to get here!
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
# 使用 Tab 自動補全
|
|
||||||
|
|
||||||
在這個關卡,你將會學到
|
|
||||||
|
|
||||||
1. 如何使用 Tab 自動補全路徑
|
|
||||||
2. 出現衝突時如何處理
|
|
||||||
|
|
||||||
按下中間的按鈕來開始這個關卡。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
在終端機裡,所有的操作都是用文字進行。
|
|
||||||
然而,打字很花時間,而且只要一個字打錯,指令就會執行失敗。
|
|
||||||
因此終端機提供「自動補全」的功能,自動幫你完成檔案路徑、指令名稱、參數名稱,節省你的時間,也避免輸入錯誤。
|
|
||||||
|
|
||||||
在開始練習之前,我們先來看看家目錄下有哪些檔案。
|
|
||||||
|
|
||||||
> 使用 `ls` 查看檔案列表
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
你看到了這個資料夾中充滿了超級長和有錯字的檔案名稱,如果這些都要我們一個一個字輸入,肯定會很痛苦。
|
|
||||||
但不要緊,只要使用自動補全,檔名不論多長都能輕鬆輸入。
|
|
||||||
|
|
||||||
使用自動補全的方法是,**先輸入部分的檔名,再按下 Tab**
|
|
||||||
(Tab 是 Q 左邊的按鍵)
|
|
||||||
例如要補全 `SuperSecretPlansForWorldDomination.txt`,你可以先輸入 `Su` 再按下 Tab,終端機就會幫你打完剩下的檔名了。
|
|
||||||
|
|
||||||
我們試看看印出 `SuperSecretPlansForWorldDomination.txt` 這個檔案的內容。
|
|
||||||
|
|
||||||
> 使用 `cat SuperSecretPlansForWorldDomination.txt` 來印出檔案內容
|
|
||||||
> 在輸入 `cat Su` 之後按下 Tab 來自動補全檔名,完成後按 Enter 就能執行指令
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
你看到了終端機自動幫你補全了 `SuperSecretPlansForWorldDomination.txt`,就算是很長的檔名,也不用怕打錯字。
|
|
||||||
開頭不論你打了幾個字都無所謂,你可以只打 `S`、`Su`、`Sup`、`Super` 或甚至完全不打字都沒關係。
|
|
||||||
但是你打的字越少,就越容易跟其他的檔案重複,這時候就要做衝突處理。
|
|
||||||
關於衝突處理,我們會在待會練習。
|
|
||||||
|
|
||||||
自動補全也可以用來對付拼字錯誤,像是 `ThsisAFlieNmaeWthAloOfTpyosThatisRleayHdToRaed.txt` 這個檔案充滿了錯字,任誰來打都難保不會失手。
|
|
||||||
這一次,請你一樣用 `cat` 來試著印出 `ThsisAFlieNmaeWthAloOfTpyosThatisRleayHdToRaed.txt` 的內容。
|
|
||||||
|
|
||||||
> 輸入 `cat ThsisAFlieNmaeWthAloOfTpyosThatisRleayHdToRaed.txt` 來印出檔案內容
|
|
||||||
> 輸入 `cat Thsi` 之後按下 Tab 做自動補全
|
|
||||||
> 試試看輸入更少的字元就按 Tab 會有什麼樣的結果
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
你看到了這個充滿錯字的檔案了。
|
|
||||||
只要使用自動補全,不管是錯字、大小寫變化、還是隨機亂碼都不再是困擾了。
|
|
||||||
讓我們再練習一個,這次請你查看 `ThingsIWillOrganizeLaterWhenIHaveTime` 資料夾中的檔案。
|
|
||||||
一樣,輸入前幾個字,然後按 Tab 自動補全。
|
|
||||||
|
|
||||||
> 輸入 `ls ThingsIWillOrganizeLaterWhenIHaveTime` 查看資料夾中的檔案
|
|
||||||
> 輸入 `ls Thing` 之後按下 Tab 做自動補全
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
你看到了這個資料夾裡有著三個檔案:一個如何做三明治的教學跟兩個在半夜隨機下載的東西。
|
|
||||||
假如我們要打開三明治教學 `HowToProperlyMakeASandwich.txt`,但是它位在資料夾中,我們沒辦法只按一次 Tab 就補全它的檔名。
|
|
||||||
這次我們就要來練習使用**連續的自動補全**。
|
|
||||||
|
|
||||||
像這樣檔案位在其他資料夾的情況,我們只要先輸入資料夾名稱的開頭,按一次 Tab,再輸入檔案的開頭,再按一次 Tab,就能夠補全 `資料夾/檔名` 了。
|
|
||||||
而且當你補全的對象是資料夾時,終端機還會自動幫你在結尾加上 `/`,省去你自己輸入的時間。
|
|
||||||
那麼我們就來打開 `HowToProperlyMakeASandwich.txt`。
|
|
||||||
|
|
||||||
> 輸入 `cat ThingsIWillOrganizeLaterWhenIHaveTime/HowToProperlyMakeASandwich.txt` 來印出檔案內容
|
|
||||||
> 先輸入 `cat Thin` 按 Tab 補全至 `cat ThingsIWillOrganizeLaterWhenIHaveTime/`
|
|
||||||
> 再輸入 `cat ThingsIWillOrganizeLaterWhenIHaveTime/How` 按 Tab 補全至完整路徑
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
你看到了這份嚴謹的三明治製作教學。
|
|
||||||
那我們剩下來要看的就是資料夾裡的兩個檔案:`RandomStuffThatIDownloadedAt3AM.txt` 和 `RandomStuffThatIDownloadedAt4AM.txt`
|
|
||||||
兩個檔名非常相似,只有最後一個是 3AM 另一個是 4AM。
|
|
||||||
這樣我們只打前面幾個字的時候,沒有辦法分辦到底選的是哪一個檔案。
|
|
||||||
這就是**發生衝突**。
|
|
||||||
|
|
||||||
遇到衝突的解決方法,其實跟使用資料夾是一樣的。
|
|
||||||
在輸入 `Ran` 按下 Tab 後,終端機會補全到衝突發生前的位置,也就是 `RandomStuffThatIDownloadedAt`。
|
|
||||||
我們只要根據我們想開啟的檔案,輸入 `3` 或是 `4`,再按一下 Tab,就能夠補全剩下的檔名了。
|
|
||||||
|
|
||||||
_如果我們把完整的路徑視作補全的對象,那資料夾其實就是一種衝突的情況。_
|
|
||||||
|
|
||||||
> 輸入 `cat ThingsIWillOrganizeLaterWhenIHaveTime/RandomStuffThatIDownloadedAt3AM.txt` 來印出檔案內容
|
|
||||||
> 先輸入 `cat Thin` 按 Tab 補全至 `cat ThingsIWillOrganizeLaterWhenIHaveTime/`
|
|
||||||
> 再接續輸入 `Ran` 按 Tab 補全至 `cat ThingsIWillOrganizeLaterWhenIHaveTime/RandomStuffThatIDownloadedAt`
|
|
||||||
> 再接續輸入 `3` 後按 Tab 補全至完整的路徑。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
你看到了一份凌晨三點下載的檔案列表。嗯…我今天網路用的夠多了。
|
|
||||||
最後留下一個挑戰給你,請先進入到 `ThingsIWillOrganizeLaterWhenIHaveTime` 資料夾後,再印出 `RandomStuffThatIDownloadedAt4AM.txt`。
|
|
||||||
練習使用今天教的自動補全來完成任務。
|
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
|
import { Session as GameSession } from '@linux-odyssey/game'
|
||||||
import { Command, Session } from '@linux-odyssey/models'
|
import { Command, Session } from '@linux-odyssey/models'
|
||||||
import { matchedData } from 'express-validator'
|
import { matchedData } from 'express-validator'
|
||||||
import type { Request, Response } from 'express'
|
import type { Request, Response } from 'express'
|
||||||
import CommandHandler from '../../game/commandHandler.js'
|
|
||||||
import { pushToSession } from '../socket.js'
|
import { pushToSession } from '../socket.js'
|
||||||
import { finishSession } from '../../models/sessionManager.js'
|
import { finishSession } from '../../models/sessionManager.js'
|
||||||
import { asyncHandler } from '../../middleware/error.js'
|
import { asyncHandler } from '../../middleware/error.js'
|
||||||
import { questManager } from '../../models/quest.js'
|
import { questManager } from '../../models/quest.js'
|
||||||
|
import { CLIFileExistenceChecker } from '../../containers/cli.js'
|
||||||
|
|
||||||
export const newCommand = asyncHandler(async (req: Request, res: Response) => {
|
export const newCommand = asyncHandler(async (req: Request, res: Response) => {
|
||||||
const { command, pwd, output, error, params } = matchedData(req)
|
const { command, pwd, output, error, params } = matchedData(req)
|
||||||
@@ -44,18 +45,26 @@ export const newCommand = asyncHandler(async (req: Request, res: Response) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const commandHandler = new CommandHandler(session, quest, c, params)
|
const gameSession = new GameSession(
|
||||||
const response = await commandHandler.run()
|
session,
|
||||||
if (response) {
|
quest,
|
||||||
c.stage = response.stage
|
new CLIFileExistenceChecker()
|
||||||
|
)
|
||||||
|
|
||||||
|
const event = await gameSession.runCommand(c)
|
||||||
|
if (event) {
|
||||||
|
c.stage = event
|
||||||
await c.save()
|
await c.save()
|
||||||
|
|
||||||
|
session.completedEvents = gameSession.completedEvents
|
||||||
|
await session.save()
|
||||||
|
|
||||||
if ((session.status as string) === 'finished') {
|
if ((session.status as string) === 'finished') {
|
||||||
await finishSession(session)
|
await finishSession(session)
|
||||||
}
|
}
|
||||||
pushToSession(session.id, 'response', response)
|
pushToSession(session.id, 'response', gameSession.getResponses())
|
||||||
|
pushToSession(session.id, 'task', gameSession.getTasks())
|
||||||
}
|
}
|
||||||
|
|
||||||
res.status(200).end()
|
res.status(200).end()
|
||||||
await session.save()
|
|
||||||
})
|
})
|
||||||
@@ -22,7 +22,7 @@ function sessionSummary(session: ISession) {
|
|||||||
function sessionDetail(session: ISession) {
|
function sessionDetail(session: ISession) {
|
||||||
return {
|
return {
|
||||||
...sessionSummary(session),
|
...sessionSummary(session),
|
||||||
stages: session.stages,
|
stages: session.events,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { execFile } from 'child_process'
|
import { execFile } from 'child_process'
|
||||||
|
|
||||||
import { buildFileCheckCmd } from '@linux-odyssey/utils'
|
import { buildFileCheckCmd } from '@linux-odyssey/utils'
|
||||||
import type { IFileCondition } from '@linux-odyssey/models'
|
import type { IFileExistenceChecker, IFileInput } from '@linux-odyssey/game'
|
||||||
|
|
||||||
function sanitizeId(id: string) {
|
function sanitizeId(id: string) {
|
||||||
return id.replace(/[^a-zA-Z0-9]/g, '')
|
return id.replace(/[^a-zA-Z0-9]/g, '')
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line import/prefer-default-export
|
// eslint-disable-next-line import/prefer-default-export
|
||||||
export function checkFile(id: string, file: IFileCondition) {
|
export function checkFile(id: string, file: IFileInput) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise<boolean>((resolve) => {
|
||||||
const cmd = buildFileCheckCmd(file)
|
const cmd = buildFileCheckCmd(file)
|
||||||
// WARNING: This is vulnerable to command injection
|
// WARNING: This is vulnerable to command injection
|
||||||
execFile(
|
execFile(
|
||||||
@@ -18,12 +18,15 @@ export function checkFile(id: string, file: IFileCondition) {
|
|||||||
{ timeout: 1000 },
|
{ timeout: 1000 },
|
||||||
(error) => {
|
(error) => {
|
||||||
const exists = !error
|
const exists = !error
|
||||||
if (exists === file.exists) {
|
resolve(exists)
|
||||||
resolve(true)
|
|
||||||
} else {
|
|
||||||
reject(new Error('File check failed'))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class CLIFileExistenceChecker implements IFileExistenceChecker {
|
||||||
|
// eslint-disable-next-line class-methods-use-this
|
||||||
|
async exists(file: IFileInput) {
|
||||||
|
return checkFile(file.path, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,7 @@
|
|||||||
import minimist from 'minimist'
|
import minimist from 'minimist'
|
||||||
import { HydratedDocument } from 'mongoose'
|
import { HydratedDocument } from 'mongoose'
|
||||||
import { FileGraph } from '@linux-odyssey/file-graph'
|
import { FileGraph } from '@linux-odyssey/file-graph'
|
||||||
import {
|
import { ICondition, Quest, Stage, IException } from '@linux-odyssey/game'
|
||||||
Condition,
|
|
||||||
FileCondition,
|
|
||||||
Quest,
|
|
||||||
Stage,
|
|
||||||
Exception,
|
|
||||||
} from '@linux-odyssey/constants'
|
|
||||||
import { ICommand, ISession } from '@linux-odyssey/models'
|
import { ICommand, ISession } from '@linux-odyssey/models'
|
||||||
import { pushToSession } from '../api/socket.js'
|
import { pushToSession } from '../api/socket.js'
|
||||||
import SessionHandler, { ExecuteResult } from './sessionHandler.js'
|
import SessionHandler, { ExecuteResult } from './sessionHandler.js'
|
||||||
|
|||||||
@@ -16,6 +16,19 @@ class QuestValidationError extends Error {
|
|||||||
this.questId = questId
|
this.questId = questId
|
||||||
this.error = error
|
this.error = error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
if (this.error?.issues) {
|
||||||
|
const issues = this.error.issues
|
||||||
|
.map((issue: any) => {
|
||||||
|
const fieldPath = issue.path.join('.')
|
||||||
|
return `- ${fieldPath}: ${issue.message}`
|
||||||
|
})
|
||||||
|
.join('\n')
|
||||||
|
return `Error in quest "${this.questId}":\n${issues}`
|
||||||
|
}
|
||||||
|
return `Error in quest "${this.questId}": ${this.message}`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class QuestManager {
|
class QuestManager {
|
||||||
@@ -49,9 +62,18 @@ class QuestManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async loadGlobalExceptions() {
|
private async loadGlobalExceptions() {
|
||||||
const exceptionsPath = path.join(this.questDirectory, 'exceptions.yml')
|
try {
|
||||||
const body = await fs.readFile(exceptionsPath, 'utf-8')
|
const exceptionsPath = path.join(this.questDirectory, 'exceptions.yml')
|
||||||
return globalExceptionSchema.array().parse(yaml.parse(body))
|
const body = await fs.readFile(exceptionsPath, 'utf-8')
|
||||||
|
return globalExceptionSchema.array().parse(yaml.parse(body))
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error loading global exceptions', { error })
|
||||||
|
throw new QuestValidationError(
|
||||||
|
'Failed to load global exceptions',
|
||||||
|
'global',
|
||||||
|
error
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadAndUpdateQuests() {
|
async loadAndUpdateQuests() {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { HydratedDocument, Types } from 'mongoose'
|
import { HydratedDocument, Types } from 'mongoose'
|
||||||
import { Session, Quest, UserProfile } from '@linux-odyssey/models'
|
import { Session, UserProfile } from '@linux-odyssey/models'
|
||||||
import type { ISession, IUser } from '@linux-odyssey/models'
|
import type { ISession, IUser } from '@linux-odyssey/models'
|
||||||
import { createContainer, deleteContainer } from '../containers/docker.js'
|
import { createContainer, deleteContainer } from '../containers/docker.js'
|
||||||
import SessionHandler from '../game/sessionHandler.js'
|
|
||||||
import logger from '../utils/logger.js'
|
import logger from '../utils/logger.js'
|
||||||
|
import { questManager } from './quest.js'
|
||||||
|
|
||||||
async function deactivateSessions(userId: Types.ObjectId, quest: string) {
|
async function deactivateSessions(userId: Types.ObjectId, quest: string) {
|
||||||
const sessions = await Session.find({
|
const sessions = await Session.find({
|
||||||
@@ -31,13 +31,13 @@ export async function createNewSession(
|
|||||||
user: HydratedDocument<IUser>,
|
user: HydratedDocument<IUser>,
|
||||||
questId: string
|
questId: string
|
||||||
): Promise<ISession> {
|
): Promise<ISession> {
|
||||||
const quest = await Quest.findById(questId)
|
const quest = await questManager.get(questId)
|
||||||
if (!quest) {
|
if (!quest) {
|
||||||
throw new Error(`Quest ${questId} not found`)
|
throw new Error(`Quest ${questId} not found`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// deactivate all active sessions
|
// deactivate all active sessions
|
||||||
deactivateSessions(user._id, quest._id).catch((err) => {
|
deactivateSessions(user._id, quest.id).catch((err) => {
|
||||||
logger.error(err)
|
logger.error(err)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -53,31 +53,24 @@ export async function createNewSession(
|
|||||||
containerId: container.id,
|
containerId: container.id,
|
||||||
})
|
})
|
||||||
|
|
||||||
const sessionHandler = new SessionHandler(newSession, quest)
|
|
||||||
|
|
||||||
sessionHandler.addNewTasks()
|
|
||||||
|
|
||||||
const session = sessionHandler.getSession()
|
|
||||||
await session.save()
|
|
||||||
|
|
||||||
const userProfile = await UserProfile.findOne({ user: user._id })
|
const userProfile = await UserProfile.findOne({ user: user._id })
|
||||||
if (!userProfile) {
|
if (!userProfile) {
|
||||||
throw new Error(`UserProfile ${user._id} not found`)
|
throw new Error(`UserProfile ${user._id} not found`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const progress = userProfile.progress.get(quest._id)
|
const progress = userProfile.progress.get(quest.id)
|
||||||
if (!progress) {
|
if (!progress) {
|
||||||
userProfile.progress.set(quest._id, {
|
userProfile.progress.set(quest.id, {
|
||||||
quest: quest._id,
|
quest: quest.id,
|
||||||
sessions: [session._id],
|
sessions: [newSession._id],
|
||||||
startedAt: new Date(),
|
startedAt: new Date(),
|
||||||
completed: false,
|
completed: false,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
progress.sessions.push(session._id)
|
progress.sessions.push(newSession._id)
|
||||||
}
|
}
|
||||||
await userProfile.save()
|
await userProfile.save()
|
||||||
return session
|
return newSession
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function finishSession(
|
export async function finishSession(
|
||||||
@@ -110,7 +103,7 @@ export async function isQuestUnlocked(
|
|||||||
if (!userProfile) {
|
if (!userProfile) {
|
||||||
throw new Error(`UserProfile ${user.id} not found`)
|
throw new Error(`UserProfile ${user.id} not found`)
|
||||||
}
|
}
|
||||||
const quest = await Quest.findById(questId)
|
const quest = await questManager.get(questId)
|
||||||
if (!quest) {
|
if (!quest) {
|
||||||
throw new Error(`Quest ${questId} not found`)
|
throw new Error(`Quest ${questId} not found`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,27 @@
|
|||||||
import { TRPCError } from '@trpc/server'
|
import { TRPCError } from '@trpc/server'
|
||||||
import { QuestDetailResponse } from '@linux-odyssey/constants'
|
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { router, publicProcedure } from '../trpc.js'
|
import { router, publicProcedure } from '../trpc.js'
|
||||||
import { questManager } from '../models/quest.js'
|
import { questManager } from '../models/quest.js'
|
||||||
|
|
||||||
export const questRouter = router({
|
export const questRouter = router({
|
||||||
getQuests: publicProcedure.query(async () => {
|
getQuests: publicProcedure.query(async () => {
|
||||||
const quests = await questManager.getAll()
|
const quests = questManager.getAll()
|
||||||
return quests.map(({ title, id, requirements }) => ({
|
return quests.map(({ title, id, requirements }) => ({
|
||||||
id,
|
id,
|
||||||
title,
|
title,
|
||||||
requirements,
|
requirements,
|
||||||
}))
|
}))
|
||||||
}),
|
}),
|
||||||
getQuestDetail: publicProcedure
|
getQuestDetail: publicProcedure.input(z.string()).query(async (opts) => {
|
||||||
.input(z.string())
|
const questId = opts.input
|
||||||
.query(async (opts): Promise<QuestDetailResponse> => {
|
const quest = questManager.get(questId)
|
||||||
const questId = opts.input
|
if (!quest) {
|
||||||
const quest = await questManager.get(questId)
|
throw new TRPCError({
|
||||||
if (!quest) {
|
code: 'NOT_FOUND',
|
||||||
throw new TRPCError({
|
message: 'Quest not found.',
|
||||||
code: 'NOT_FOUND',
|
})
|
||||||
message: 'Quest not found.',
|
}
|
||||||
})
|
const { id, title, instruction, requirements } = quest
|
||||||
}
|
return { id, title, instruction, requirements }
|
||||||
const { id, title, instruction, requirements } = quest
|
}),
|
||||||
return { id, title, instruction, requirements }
|
|
||||||
}),
|
|
||||||
})
|
})
|
||||||
@@ -34,7 +34,7 @@ export const sessionRouter = router({
|
|||||||
status: session.status,
|
status: session.status,
|
||||||
createdAt: session.createdAt,
|
createdAt: session.createdAt,
|
||||||
lastActivityAt: session.lastActivityAt,
|
lastActivityAt: session.lastActivityAt,
|
||||||
stages: session.stages,
|
stages: session.events,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
|
|||||||
Reference in new issue
Block a user